summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/Makefile.am139
-rw-r--r--tools/afcclient.c1664
-rw-r--r--tools/idevice_id.c169
-rw-r--r--tools/idevicebackup.c664
-rw-r--r--tools/idevicebackup2.c2684
-rw-r--r--tools/idevicebtlogger.c458
-rw-r--r--tools/idevicecrashreport.c529
-rw-r--r--tools/idevicedate.c265
-rw-r--r--tools/idevicedebug.c625
-rw-r--r--tools/idevicedebugserverproxy.c375
-rw-r--r--tools/idevicedevmodectl.c462
-rw-r--r--tools/idevicediagnostics.c344
-rw-r--r--tools/ideviceenterrecovery.c128
-rw-r--r--tools/ideviceimagemounter.c873
-rw-r--r--tools/ideviceinfo.c363
-rw-r--r--tools/idevicename.c159
-rw-r--r--tools/idevicenotificationproxy.c293
-rw-r--r--tools/idevicepair.c473
-rw-r--r--tools/ideviceprovision.c689
-rw-r--r--tools/idevicescreenshot.c259
-rw-r--r--tools/idevicesetlocation.c192
-rw-r--r--tools/idevicesyslog.c812
22 files changed, 11237 insertions, 1382 deletions
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 0a47fdc..b0c2769 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -1,51 +1,144 @@
-AM_CPPFLAGS = -I$(top_srcdir)/include
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)
-AM_CFLAGS = $(GLOBAL_CFLAGS) $(libglib2_CFLAGS) $(libgnutls_CFLAGS) $(libtasn1_CFLAGS) $(libgthread2_CFLAGS) $(LFS_CFLAGS)
-AM_LDFLAGS = $(libglib2_LIBS) $(libgnutls_LIBS) $(libtasn1_LIBS) $(libgthread2_LIBS)
+AM_CFLAGS = \
+ $(GLOBAL_CFLAGS) \
+ $(ssl_lib_CFLAGS) \
+ $(libplist_CFLAGS) \
+ $(LFS_CFLAGS)
-bin_PROGRAMS = idevice_id ideviceinfo idevicepair idevicesyslog idevicebackup ideviceimagemounter idevicescreenshot ideviceenterrecovery idevicedate
+AM_LDFLAGS = \
+ $(libplist_LIBS)
+
+bin_PROGRAMS = \
+ idevicebtlogger\
+ idevice_id \
+ ideviceinfo \
+ idevicename \
+ idevicepair \
+ idevicesyslog \
+ ideviceimagemounter \
+ idevicescreenshot \
+ ideviceenterrecovery \
+ idevicedate \
+ idevicebackup \
+ idevicebackup2 \
+ ideviceprovision \
+ idevicedebugserverproxy \
+ idevicediagnostics \
+ idevicedebug \
+ idevicedevmodectl \
+ idevicenotificationproxy \
+ idevicecrashreport \
+ idevicesetlocation \
+ afcclient
+
+idevicebtlogger_SOURCES = idevicebtlogger.c
+idevicebtlogger_CFLAGS = $(AM_CFLAGS)
+idevicebtlogger_LDFLAGS = $(top_builddir)/common/libinternalcommon.la $(AM_LDFLAGS)
+idevicebtlogger_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
ideviceinfo_SOURCES = ideviceinfo.c
-ideviceinfo_CFLAGS = $(AM_CFLAGS)
-ideviceinfo_LDFLAGS = $(AM_LDFLAGS)
-ideviceinfo_LDADD = ../src/libimobiledevice.la
+ideviceinfo_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS)
+ideviceinfo_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS)
+ideviceinfo_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
+
+idevicename_SOURCES = idevicename.c
+idevicename_CFLAGS = $(AM_CFLAGS)
+idevicename_LDFLAGS = $(AM_LDFLAGS)
+idevicename_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
idevicepair_SOURCES = idevicepair.c
-idevicepair_CFLAGS = $(AM_CFLAGS) -I$(top_srcdir)/src
-idevicepair_LDFLAGS = $(AM_LDFLAGS)
-idevicepair_LDADD = ../src/libimobiledevice.la
+idevicepair_CFLAGS = $(AM_CFLAGS)
+idevicepair_LDFLAGS = $(AM_LDFLAGS) $(libusbmuxd_LIBS) $(ssl_lib_LIBS)
+idevicepair_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la $(top_builddir)/common/libinternalcommon.la $(limd_glue_LIBS)
idevicesyslog_SOURCES = idevicesyslog.c
-idevicesyslog_CFLAGS = $(AM_CFLAGS)
-idevicesyslog_LDFLAGS = $(AM_LDFLAGS)
-idevicesyslog_LDADD = ../src/libimobiledevice.la
+idevicesyslog_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS)
+idevicesyslog_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS)
+idevicesyslog_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
idevice_id_SOURCES = idevice_id.c
idevice_id_CFLAGS = $(AM_CFLAGS)
idevice_id_LDFLAGS = $(AM_LDFLAGS)
-idevice_id_LDADD = ../src/libimobiledevice.la
+idevice_id_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
idevicebackup_SOURCES = idevicebackup.c
-idevicebackup_CFLAGS = $(AM_CFLAGS)
-idevicebackup_LDFLAGS = $(AM_LDFLAGS)
-idevicebackup_LDADD = ../src/libimobiledevice.la
+idevicebackup_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS)
+idevicebackup_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS)
+idevicebackup_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
+
+idevicebackup2_SOURCES = idevicebackup2.c
+idevicebackup2_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS)
+idevicebackup2_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS)
+idevicebackup2_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
ideviceimagemounter_SOURCES = ideviceimagemounter.c
-ideviceimagemounter_CFLAGS = $(AM_CFLAGS)
-ideviceimagemounter_LDFLAGS = $(AM_LDFLAGS)
-ideviceimagemounter_LDADD = ../src/libimobiledevice.la
+ideviceimagemounter_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS) $(libtatsu_CFLAGS)
+ideviceimagemounter_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS) $(ssl_lib_LIBS) $(libtatsu_LIBS)
+ideviceimagemounter_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
idevicescreenshot_SOURCES = idevicescreenshot.c
idevicescreenshot_CFLAGS = $(AM_CFLAGS)
idevicescreenshot_LDFLAGS = $(AM_LDFLAGS)
-idevicescreenshot_LDADD = ../src/libimobiledevice.la
+idevicescreenshot_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
ideviceenterrecovery_SOURCES = ideviceenterrecovery.c
ideviceenterrecovery_CFLAGS = $(AM_CFLAGS)
ideviceenterrecovery_LDFLAGS = $(AM_LDFLAGS)
-ideviceenterrecovery_LDADD = ../src/libimobiledevice.la
+ideviceenterrecovery_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
idevicedate_SOURCES = idevicedate.c
idevicedate_CFLAGS = $(AM_CFLAGS)
idevicedate_LDFLAGS = $(AM_LDFLAGS)
-idevicedate_LDADD = ../src/libimobiledevice.la
+idevicedate_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
+
+ideviceprovision_SOURCES = ideviceprovision.c
+ideviceprovision_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS)
+ideviceprovision_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS)
+ideviceprovision_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
+
+idevicedebugserverproxy_SOURCES = idevicedebugserverproxy.c
+idevicedebugserverproxy_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS)
+idevicedebugserverproxy_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS)
+idevicedebugserverproxy_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
+
+idevicediagnostics_SOURCES = idevicediagnostics.c
+idevicediagnostics_CFLAGS = $(AM_CFLAGS)
+idevicediagnostics_LDFLAGS = $(AM_LDFLAGS)
+idevicediagnostics_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
+
+idevicedebug_SOURCES = idevicedebug.c
+idevicedebug_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS)
+idevicedebug_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS)
+idevicedebug_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la $(top_builddir)/common/libinternalcommon.la
+
+idevicedevmodectl_SOURCES = idevicedevmodectl.c
+idevicedevmodectl_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS)
+idevicedevmodectl_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS)
+idevicedevmodectl_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la $(top_builddir)/common/libinternalcommon.la
+
+idevicenotificationproxy_SOURCES = idevicenotificationproxy.c
+idevicenotificationproxy_CFLAGS = $(AM_CFLAGS)
+idevicenotificationproxy_LDFLAGS = $(AM_LDFLAGS)
+idevicenotificationproxy_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
+
+idevicecrashreport_SOURCES = idevicecrashreport.c
+idevicecrashreport_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS)
+idevicecrashreport_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS)
+idevicecrashreport_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
+
+idevicesetlocation_SOURCES = idevicesetlocation.c
+idevicesetlocation_CFLAGS = $(AM_CFLAGS)
+idevicesetlocation_LDFLAGS = $(AM_LDFLAGS)
+idevicesetlocation_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
+
+afcclient_SOURCES = afcclient.c
+afcclient_CFLAGS = $(AM_CFLAGS)
+afcclient_LDFLAGS = $(AM_LDFLAGS)
+if HAVE_READLINE
+ afcclient_CFLAGS += $(readline_CFLAGS)
+ afcclient_LDFLAGS += $(readline_LIBS)
+endif
+afcclient_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la $(limd_glue_LIBS)
diff --git a/tools/afcclient.c b/tools/afcclient.c
new file mode 100644
index 0000000..8f49831
--- /dev/null
+++ b/tools/afcclient.c
@@ -0,0 +1,1664 @@
+/*
+ * afcclient.c
+ * Utility to interact with AFC/HoustArrest service on the device
+ *
+ * Inspired by https://github.com/emonti/afcclient
+ * But entirely rewritten from scratch.
+ *
+ * Copyright (c) 2023 Nikias Bassen, All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "afcclient"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <signal.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <time.h>
+
+#ifdef WIN32
+#include <windows.h>
+#include <sys/time.h>
+#include <conio.h>
+#define sleep(x) Sleep(x*1000)
+#define S_IFMT 0170000 /* [XSI] type of file mask */
+#define S_IFIFO 0010000 /* [XSI] named pipe (fifo) */
+#define S_IFCHR 0020000 /* [XSI] character special */
+#define S_IFBLK 0060000 /* [XSI] block special */
+#define S_IFLNK 0120000 /* [XSI] symbolic link */
+#define S_IFSOCK 0140000 /* [XSI] socket */
+#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) /* block special */
+#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) /* char special */
+#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) /* fifo or socket */
+#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) /* symbolic link */
+#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) /* socket */
+#else
+#include <sys/time.h>
+#include <termios.h>
+#endif
+
+#ifdef HAVE_READLINE
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif
+
+#include <libimobiledevice/libimobiledevice.h>
+#include <libimobiledevice/lockdown.h>
+#include <libimobiledevice/house_arrest.h>
+#include <libimobiledevice/afc.h>
+#include <plist/plist.h>
+
+#include <libimobiledevice-glue/termcolors.h>
+#include <libimobiledevice-glue/utils.h>
+
+#undef st_mtime
+#undef st_birthtime
+struct afc_file_stat {
+ uint16_t st_mode;
+ uint16_t st_nlink;
+ uint64_t st_size;
+ uint64_t st_mtime;
+ uint64_t st_birthtime;
+ uint32_t st_blocks;
+};
+
+static char* udid = NULL;
+static int connected = 0;
+static int use_network = 0;
+static idevice_subscription_context_t context = NULL;
+static char* curdir = NULL;
+static size_t curdir_len = 0;
+
+static int file_exists(const char* path)
+{
+ struct stat tst;
+#ifdef WIN32
+ return (stat(path, &tst) == 0);
+#else
+ return (lstat(path, &tst) == 0);
+#endif
+}
+
+static int is_directory(const char* path)
+{
+ struct stat tst;
+#ifdef WIN32
+ return (stat(path, &tst) == 0) && S_ISDIR(tst.st_mode);
+#else
+ return (lstat(path, &tst) == 0) && S_ISDIR(tst.st_mode);
+#endif
+}
+
+static void print_usage(int argc, char **argv, int is_error)
+{
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Interact with AFC/HouseArrest service on a connected device.\n"
+ "\n"
+ "OPTIONS:\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ " -n, --network connect to network device (not recommended!)\n"
+ " --container <appid> Access container of given app\n"
+ " --documents <appid> Access Documents directory of given app\n"
+ " -h, --help prints usage information\n" \
+ " -d, --debug enable communication debugging\n" \
+ " -v, --version prints version information\n" \
+ "\n"
+ );
+ fprintf(is_error ? stderr : stdout,
+ "\n" \
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
+}
+
+#ifndef HAVE_READLINE
+#ifdef WIN32
+#define BS_CC '\b'
+#else
+#define BS_CC 0x7f
+#define getch getchar
+#endif
+static void get_input(char *buf, int maxlen)
+{
+ int len = 0;
+ int c;
+
+ while ((c = getch())) {
+ if ((c == '\r') || (c == '\n')) {
+ break;
+ }
+ if (isprint(c)) {
+ if (len < maxlen-1)
+ buf[len++] = c;
+ } else if (c == BS_CC) {
+ if (len > 0) {
+ fputs("\b \b", stdout);
+ len--;
+ }
+ }
+ }
+ buf[len] = 0;
+}
+#endif
+
+#define OPT_DOCUMENTS 1
+#define OPT_CONTAINER 2
+
+int stop_requested = 0;
+
+static void handle_signal(int sig)
+{
+ stop_requested++;
+#ifdef WIN32
+ GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
+#else
+ kill(getpid(), SIGINT);
+#endif
+}
+
+static void handle_help(afc_client_t afc, int argc, char** argv)
+{
+ printf("Available commands:\n");
+ printf("help - print list of available commands\n");
+ printf("devinfo - print device information\n");
+ printf("info PATH - print file attributes of file at PATH\n");
+ printf("ls [-l] PATH - print directory contents of PATH\n");
+ printf("mv OLD NEW - rename file OLD to NEW\n");
+ printf("mkdir PATH - create directory at PATH\n");
+ printf("ln [-s] FILE [LINK] - create a (symbolic) link to file named LINKNAME\n");
+ printf(" NOTE: This feature has been disabled in newer versions of iOS.\n");
+ printf("rm PATH - remove item at PATH\n");
+ printf("get [-rf] PATH [LOCALPATH] - transfer file at PATH from device to LOCALPATH\n");
+ printf("put [-rf] LOCALPATH [PATH] - transfer local file at LOCALPATH to device at PATH\n");
+ printf("\n");
+}
+
+static const char* path_get_basename(const char* path)
+{
+ const char *p = strrchr(path, '/');
+ return p ? p + 1 : path;
+}
+
+static int timeval_subtract(struct timeval *result, struct timeval *x, struct timeval *y)
+{
+ /* Perform the carry for the later subtraction by updating y. */
+ if (x->tv_usec < y->tv_usec) {
+ int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
+ y->tv_usec -= 1000000 * nsec;
+ y->tv_sec += nsec;
+ }
+ if (x->tv_usec - y->tv_usec > 1000000) {
+ int nsec = (x->tv_usec - y->tv_usec) / 1000000;
+ y->tv_usec += 1000000 * nsec;
+ y->tv_sec -= nsec;
+ }
+ /* Compute the time remaining to wait.
+ tv_usec is certainly positive. */
+ result->tv_sec = x->tv_sec - y->tv_sec;
+ result->tv_usec = x->tv_usec - y->tv_usec;
+ /* Return 1 if result is negative. */
+ return x->tv_sec < y->tv_sec;
+}
+
+struct str_item {
+ size_t len;
+ char* str;
+};
+
+static char* get_absolute_path(const char *path)
+{
+ if (*path == '/') {
+ return strdup(path);
+ } else {
+ size_t len = curdir_len + 1 + strlen(path) + 1;
+ char* result = (char*)malloc(len);
+ if (!strcmp(curdir, "/")) {
+ snprintf(result, len, "/%s", path);
+ } else {
+ snprintf(result, len, "%s/%s", curdir, path);
+ }
+ return result;
+ }
+}
+
+static char* get_realpath(const char* path)
+{
+ if (!path) return NULL;
+
+ int is_absolute = 0;
+ if (*path == '/') {
+ is_absolute = 1;
+ }
+
+ const char* p = path;
+ if (is_absolute) {
+ while (*p == '/') p++;
+ }
+ if (*p == '\0') {
+ return strdup("/");
+ }
+
+ int c_count = 1;
+ const char* start = p;
+ const char* end = p;
+ struct str_item* comps = NULL;
+
+ while (*p) {
+ if (*p == '/') {
+ p++;
+ end = p-1;
+ while (*p == '/') p++;
+ if (*p == '\0') break;
+ struct str_item* newcomps = (struct str_item*)realloc(comps, sizeof(struct str_item)*c_count);
+ if (!newcomps) {
+ free(comps);
+ printf("%s: out of memory?!\n", __func__);
+ return NULL;
+ }
+ comps = newcomps;
+ char *comp = (char*)malloc(end-start+1);
+ strncpy(comp, start, end-start);
+ comp[end-start] = '\0';
+ comps[c_count-1].len = end-start;
+ comps[c_count-1].str = comp;
+ c_count++;
+ start = p;
+ end = p;
+ }
+ p++;
+ }
+ if (p > start) {
+ if (start == end) {
+ end = p;
+ }
+ struct str_item* newcomps = (struct str_item*)realloc(comps, sizeof(struct str_item)*c_count);
+ if (!newcomps) {
+ free(comps);
+ printf("%s: out of memory?!\n", __func__);
+ return NULL;
+ }
+ comps = newcomps;
+ char *comp = (char*)malloc(end-start+1);
+ strncpy(comp, start, end-start);
+ comp[end-start] = '\0';
+ comps[c_count-1].len = end-start;
+ comps[c_count-1].str = comp;
+ }
+
+ struct str_item* comps_final = (struct str_item*)malloc(sizeof(struct str_item)*(c_count+1));
+ int o = 1;
+ if (is_absolute) {
+ comps_final[0].len = 1;
+ comps_final[0].str = (char*)"/";
+ } else {
+ comps_final[0].len = curdir_len;
+ comps_final[0].str = curdir;
+ }
+ size_t o_len = comps_final[0].len;
+
+ for (int i = 0; i < c_count; i++) {
+ if (!strcmp(comps[i].str, "..")) {
+ o--;
+ continue;
+ } else if (!strcmp(comps[i].str, ".")) {
+ continue;
+ }
+ o_len += comps[i].len;
+ comps_final[o].str = comps[i].str;
+ comps_final[o].len = comps[i].len;
+ o++;
+ }
+
+ o_len += o;
+ char* result = (char*)malloc(o_len);
+ char* presult = result;
+ for (int i = 0; i < o; i++) {
+ if (i > 0 && strcmp(comps_final[i-1].str, "/") != 0) {
+ *presult = '/';
+ presult++;
+ }
+ strncpy(presult, comps_final[i].str, comps_final[i].len);
+ presult+=comps_final[i].len;
+ *presult = '\0';
+ }
+ if (presult == result) {
+ *presult = '/';
+ presult++;
+ *presult = 0;
+ }
+
+ for (int i = 0; i < c_count; i++) {
+ free(comps[i].str);
+ }
+ free(comps);
+ free(comps_final);
+
+ return result;
+}
+
+static void handle_devinfo(afc_client_t afc, int argc, char** argv)
+{
+ char **info = NULL;
+ afc_error_t err = afc_get_device_info(afc, &info);
+ if (err == AFC_E_SUCCESS && info) {
+ int i;
+ for (i = 0; info[i]; i += 2) {
+ printf("%s: %s\n", info[i], info[i+1]);
+ }
+ } else {
+ printf("Error: Failed to get device info: %s (%d)\n", afc_strerror(err), err);
+ }
+ afc_dictionary_free(info);
+}
+
+static int get_file_info_stat(afc_client_t afc, const char* path, struct afc_file_stat *stbuf)
+{
+ char **info = NULL;
+ afc_error_t ret = afc_get_file_info(afc, path, &info);
+ memset(stbuf, 0, sizeof(struct afc_file_stat));
+ if (ret != AFC_E_SUCCESS) {
+ return -1;
+ } else if (!info) {
+ return -1;
+ } else {
+ // get file attributes from info list
+ int i;
+ for (i = 0; info[i]; i += 2) {
+ if (!strcmp(info[i], "st_size")) {
+ stbuf->st_size = atoll(info[i+1]);
+ } else if (!strcmp(info[i], "st_blocks")) {
+ stbuf->st_blocks = atoi(info[i+1]);
+ } else if (!strcmp(info[i], "st_ifmt")) {
+ if (!strcmp(info[i+1], "S_IFREG")) {
+ stbuf->st_mode = S_IFREG;
+ } else if (!strcmp(info[i+1], "S_IFDIR")) {
+ stbuf->st_mode = S_IFDIR;
+ } else if (!strcmp(info[i+1], "S_IFLNK")) {
+ stbuf->st_mode = S_IFLNK;
+ } else if (!strcmp(info[i+1], "S_IFBLK")) {
+ stbuf->st_mode = S_IFBLK;
+ } else if (!strcmp(info[i+1], "S_IFCHR")) {
+ stbuf->st_mode = S_IFCHR;
+ } else if (!strcmp(info[i+1], "S_IFIFO")) {
+ stbuf->st_mode = S_IFIFO;
+ } else if (!strcmp(info[i+1], "S_IFSOCK")) {
+ stbuf->st_mode = S_IFSOCK;
+ }
+ } else if (!strcmp(info[i], "st_nlink")) {
+ stbuf->st_nlink = atoi(info[i+1]);
+ } else if (!strcmp(info[i], "st_mtime")) {
+ stbuf->st_mtime = (time_t)(atoll(info[i+1]) / 1000000000);
+ } else if (!strcmp(info[i], "st_birthtime")) { /* available on iOS 7+ */
+ stbuf->st_birthtime = (time_t)(atoll(info[i+1]) / 1000000000);
+ }
+ }
+ afc_dictionary_free(info);
+ }
+ return 0;
+}
+
+static void handle_file_info(afc_client_t afc, int argc, char** argv)
+{
+ if (argc < 1) {
+ printf("Error: Missing PATH.\n");
+ return;
+ }
+
+ char **info = NULL;
+ char* abspath = get_absolute_path(argv[0]);
+ if (!abspath) {
+ printf("Error: Invalid argument\n");
+ return;
+ }
+ afc_error_t err = afc_get_file_info(afc, abspath, &info);
+ if (err == AFC_E_SUCCESS && info) {
+ int i;
+ for (i = 0; info[i]; i += 2) {
+ printf("%s: %s\n", info[i], info[i+1]);
+ }
+ } else {
+ printf("Error: Failed to get file info for %s: %s (%d)\n", argv[0], afc_strerror(err), err);
+ }
+ afc_dictionary_free(info);
+ free(abspath);
+}
+
+static void print_file_info(afc_client_t afc, const char* path, int list_verbose)
+{
+ struct afc_file_stat st;
+ get_file_info_stat(afc, path, &st);
+ if (list_verbose) {
+ char timebuf[64];
+ time_t t = st.st_mtime;
+ if (S_ISDIR(st.st_mode)) {
+ printf("drwxr-xr-x");
+ } else if (S_ISLNK(st.st_mode)) {
+ printf("lrwxrwxrwx");
+ } else {
+ if (S_ISFIFO(st.st_mode)) {
+ printf("f");
+ } else if (S_ISBLK(st.st_mode)) {
+ printf("b");
+ } else if (S_ISCHR(st.st_mode)) {
+ printf("c");
+ } else if (S_ISSOCK(st.st_mode)) {
+ printf("s");
+ } else {
+ printf("-");
+ }
+ printf("rw-r--r--");
+ }
+ printf(" ");
+ printf("%4d", st.st_nlink);
+ printf(" ");
+ printf("mobile");
+ printf(" ");
+ printf("mobile");
+ printf(" ");
+ printf("%10lld", (long long)st.st_size);
+ printf(" ");
+#ifdef WIN32
+ strftime(timebuf, 64, "%d %b %Y %H:%M:%S", localtime(&t));
+#else
+ strftime(timebuf, 64, "%d %h %Y %H:%M:%S", localtime(&t));
+#endif
+ printf("%s", timebuf);
+ printf(" ");
+ }
+ if (S_ISDIR(st.st_mode)) {
+ cprintf(FG_CYAN);
+ } else if (S_ISLNK(st.st_mode)) {
+ cprintf(FG_MAGENTA);
+ } else if (S_ISREG(st.st_mode)) {
+ cprintf(FG_DEFAULT);
+ } else {
+ cprintf(FG_YELLOW);
+ }
+ cprintf("%s" COLOR_RESET "\n", path_get_basename(path));
+}
+
+static void handle_list(afc_client_t afc, int argc, char** argv)
+{
+ const char* path = NULL;
+ int list_verbose = 0;
+ if (argc < 1) {
+ path = curdir;
+ } else {
+ if (!strcmp(argv[0], "-l")) {
+ list_verbose = 1;
+ if (argc == 2) {
+ path = argv[1];
+ } else {
+ path = curdir;
+ }
+ } else {
+ path = argv[0];
+ }
+ }
+ char* abspath = get_absolute_path(path);
+ if (!abspath) {
+ printf("Error: Invalid argument\n");
+ return;
+ }
+ int abspath_is_root = strcmp(abspath, "/") == 0;
+ size_t abspath_len = (abspath_is_root) ? 0 : strlen(abspath);
+ char** entries = NULL;
+ afc_error_t err = afc_read_directory(afc, abspath, &entries);
+ if (err == AFC_E_READ_ERROR) {
+ print_file_info(afc, abspath, list_verbose);
+ return;
+ } else if (err != AFC_E_SUCCESS) {
+ printf("Error: Failed to list '%s': %s (%d)\n", path, afc_strerror(err), err);
+ free(abspath);
+ return;
+ }
+
+ char** p = entries;
+ while (p && *p) {
+ if (strcmp(".", *p) == 0 || strcmp("..", *p) == 0) {
+ p++;
+ continue;
+ }
+ size_t len = abspath_len + 1 + strlen(*p) + 1;
+ char* testpath = (char*)malloc(len);
+ if (abspath_is_root) {
+ snprintf(testpath, len, "/%s", *p);
+ } else {
+ snprintf(testpath, len, "%s/%s", abspath, *p);
+ }
+ print_file_info(afc, testpath, list_verbose);
+ free(testpath);
+ p++;
+ }
+ afc_dictionary_free(entries);
+ free(abspath);
+}
+
+static void handle_rename(afc_client_t afc, int argc, char** argv)
+{
+ if (argc != 2) {
+ printf("Error: Invalid number of arguments\n");
+ return;
+ }
+ char* srcpath = get_absolute_path(argv[0]);
+ if (!srcpath) {
+ printf("Error: Invalid argument\n");
+ return;
+ }
+ char* dstpath = get_absolute_path(argv[1]);
+ if (!dstpath) {
+ free(srcpath);
+ printf("Error: Invalid argument\n");
+ return;
+ }
+ afc_error_t err = afc_rename_path(afc, srcpath, dstpath);
+ if (err != AFC_E_SUCCESS) {
+ printf("Error: Failed to rename '%s' -> '%s': %s (%d)\n", argv[0], argv[1], afc_strerror(err), err);
+ }
+ free(srcpath);
+ free(dstpath);
+}
+
+static void handle_mkdir(afc_client_t afc, int argc, char** argv)
+{
+ for (int i = 0; i < argc; i++) {
+ char* abspath = get_absolute_path(argv[i]);
+ if (!abspath) {
+ printf("Error: Invalid argument '%s'\n", argv[i]);
+ continue;
+ }
+ afc_error_t err = afc_make_directory(afc, abspath);
+ if (err != AFC_E_SUCCESS) {
+ printf("Error: Failed to create directory '%s': %s (%d)\n", argv[i], afc_strerror(err), err);
+ }
+ free(abspath);
+ }
+}
+
+static void handle_link(afc_client_t afc, int argc, char** argv)
+{
+ if (argc < 2) {
+ printf("Error: Invalid number of arguments\n");
+ return;
+ }
+ afc_link_type_t link_type = AFC_HARDLINK;
+ if (!strcmp(argv[0], "-s")) {
+ argc--;
+ argv++;
+ link_type = AFC_SYMLINK;
+ }
+ if (argc < 1 || argc > 2) {
+ printf("Error: Invalid number of arguments\n");
+ return;
+ }
+ const char *link_name = (argc == 1) ? path_get_basename(argv[0]) : argv[1];
+ char* abs_link_name = get_absolute_path(link_name);
+ if (!abs_link_name) {
+ printf("Error: Invalid argument\n");
+ return;
+ }
+ afc_error_t err = afc_make_link(afc, link_type, argv[0], link_name);
+ if (err != AFC_E_SUCCESS) {
+ printf("Error: Failed to create %s link for '%s' at '%s': %s (%d)\n", (link_type == AFC_HARDLINK) ? "hard" : "symbolic", argv[0], link_name, afc_strerror(err), err);
+ }
+}
+
+static int ask_yesno(const char* prompt)
+{
+ int ret = 0;
+#ifdef HAVE_READLINE
+ char* result = readline(prompt);
+ if (result && result[0] == 'y') {
+ ret = 1;
+ }
+#else
+ char cmdbuf[2] = {0, };
+ printf("%s", prompt);
+ fflush(stdout);
+ get_input(cmdbuf, sizeof(cmdbuf));
+ if (cmdbuf[0] == 'y') {
+ ret = 1;
+ }
+#endif
+#ifdef HAVE_READLINE
+ free(result);
+#endif
+ return ret;
+}
+
+static void handle_remove(afc_client_t afc, int argc, char** argv)
+{
+ int recursive = 0;
+ int force = 0;
+ int i = 0;
+ for (i = 0; i < argc; i++) {
+ if (!strcmp(argv[i], "--")) {
+ i++;
+ break;
+ } else if (!strcmp(argv[i], "-r")) {
+ recursive = 1;
+ } else if (!strcmp(argv[i], "-f")) {
+ force = 1;
+ } else if (!strcmp(argv[i], "-rf") || !strcmp(argv[i], "-fr")) {
+ recursive = 1;
+ force = 1;
+ } else {
+ break;
+ }
+ }
+ if (recursive && !force) {
+ if (!ask_yesno("WARNING: This operation will remove all contents of the given path(s). Continue? [y/N] ")) {
+ printf("Aborted.\n");
+ return;
+ }
+ }
+ for ( ; i < argc; i++) {
+ char* abspath = get_absolute_path(argv[i]);
+ if (!abspath) {
+ printf("Error: Invalid argument '%s'\n", argv[i]);
+ continue;
+ }
+ afc_error_t err;
+ if (recursive) {
+ err = afc_remove_path_and_contents(afc, abspath);
+ } else {
+ err = afc_remove_path(afc, abspath);
+ }
+ if (err != AFC_E_SUCCESS) {
+ printf("Error: Failed to remove '%s': %s (%d)\n", argv[i], afc_strerror(err), err);
+ }
+ free(abspath);
+ }
+}
+
+static uint8_t get_single_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint64_t file_size, uint8_t force_overwrite)
+{
+ uint64_t fh = 0;
+ afc_error_t err = afc_file_open(afc, srcpath, AFC_FOPEN_RDONLY, &fh);
+ if (err != AFC_E_SUCCESS) {
+ printf("Error: Failed to open file '%s': %s (%d)\n", srcpath, afc_strerror(err), err);
+ return 0;
+ }
+ if (file_exists(dstpath) && !force_overwrite) {
+ printf("Error: Failed to overwrite existing file without '-f' option: %s\n", dstpath);
+ return 0;
+ }
+ FILE *f = fopen(dstpath, "wb");
+ if (!f) {
+ printf("Error: Failed to open local file '%s': %s\n", dstpath, strerror(errno));
+ return 0;
+ }
+ struct timeval t1;
+ struct timeval t2;
+ struct timeval tdiff;
+ size_t bufsize = 0x100000;
+ char *buf = malloc(bufsize);
+ size_t total = 0;
+ int progress = 0;
+ int lastprog = 0;
+ if (file_size > 0x400000) {
+ progress = 1;
+ gettimeofday(&t1, NULL);
+ }
+ uint8_t succeed = 1;
+ while (err == AFC_E_SUCCESS) {
+ uint32_t bytes_read = 0;
+ size_t chunk = 0;
+ err = afc_file_read(afc, fh, buf, bufsize, &bytes_read);
+ if (bytes_read == 0) {
+ break;
+ }
+ while (chunk < bytes_read) {
+ size_t wr = fwrite(buf + chunk, 1, bytes_read - chunk, f);
+ if (wr == 0) {
+ if (progress) {
+ printf("\n");
+ }
+ printf("Error: Failed to write to local file\n");
+ succeed = 0;
+ break;
+ }
+ chunk += wr;
+ }
+ total += chunk;
+ if (progress) {
+ int prog = (int) ((double) total / (double) file_size * 100.0f);
+ if (prog > lastprog) {
+ gettimeofday(&t2, NULL);
+ timeval_subtract(&tdiff, &t2, &t1);
+ double time_in_sec = (double) tdiff.tv_sec + (double) tdiff.tv_usec / 1000000;
+ printf("\r%d%% (%0.1f MB/s) ", prog, (double) total / 1048576.0f / time_in_sec);
+ fflush(stdout);
+ lastprog = prog;
+ }
+ }
+ }
+ if (progress) {
+ printf("\n");
+ }
+ if (err != AFC_E_SUCCESS) {
+ printf("Error: Failed to read from file '%s': %s (%d)\n", srcpath, afc_strerror(err), err);
+ succeed = 0;
+ }
+ free(buf);
+ fclose(f);
+ afc_file_close(afc, fh);
+ return succeed;
+}
+
+static int __mkdir(const char* path)
+{
+#ifdef WIN32
+ return mkdir(path);
+#else
+ return mkdir(path, 0755);
+#endif
+}
+
+static uint8_t get_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint8_t force_overwrite, uint8_t recursive_get)
+{
+ char **info = NULL;
+ uint64_t file_size = 0;
+ afc_error_t err = afc_get_file_info(afc, srcpath, &info);
+ if (err == AFC_E_OBJECT_NOT_FOUND) {
+ printf("Error: Failed to read from file '%s': %s (%d)\n", srcpath, afc_strerror(err), err);
+ return 0;
+ }
+ uint8_t is_dir = 0;
+ if (info) {
+ char **p = info;
+ while (p && *p) {
+ if (!strcmp(*p, "st_size")) {
+ p++;
+ file_size = (uint64_t) strtoull(*p, NULL, 10);
+ }
+ if (!strcmp(*p, "st_ifmt")) {
+ p++;
+ is_dir = !strcmp(*p, "S_IFDIR");
+ }
+ p++;
+ }
+ afc_dictionary_free(info);
+ }
+ uint8_t succeed = 1;
+ if (is_dir) {
+ if (!recursive_get) {
+ printf("Error: Failed to get a directory without '-r' option: %s\n", srcpath);
+ return 0;
+ }
+ char **entries = NULL;
+ err = afc_read_directory(afc, srcpath, &entries);
+ if (err != AFC_E_SUCCESS) {
+ printf("Error: Failed to list '%s': %s (%d)\n", srcpath, afc_strerror(err), err);
+ return 0;
+ }
+ char **p = entries;
+ size_t srcpath_len = strlen(srcpath);
+ uint8_t srcpath_is_root = strcmp(srcpath, "/") == 0;
+ // if directory exists, check force_overwrite flag
+ if (is_directory(dstpath)) {
+ if (!force_overwrite) {
+ printf("Error: Failed to write into existing directory without '-f': %s\n", dstpath);
+ return 0;
+ }
+ } else if (__mkdir(dstpath) != 0) {
+ printf("Error: Failed to create directory '%s': %s\n", dstpath, strerror(errno));
+ afc_dictionary_free(entries);
+ return 0;
+ }
+ while (p && *p) {
+ if (strcmp(".", *p) == 0 || strcmp("..", *p) == 0) {
+ p++;
+ continue;
+ }
+ size_t len = srcpath_is_root ? strlen(*p) + 1 : srcpath_len + 1 + strlen(*p) + 1;
+ char *testpath = (char *) malloc(len);
+ if (srcpath_is_root) {
+ snprintf(testpath, len, "/%s", *p);
+ } else {
+ snprintf(testpath, len, "%s/%s", srcpath, *p);
+ }
+ uint8_t dst_is_root = strcmp(srcpath, "/") == 0;
+ size_t dst_len = dst_is_root ? strlen(*p) + 1 : strlen(dstpath) + 1 + strlen(*p) + 1;
+ char *newdst = (char *) malloc(dst_len);
+ if (dst_is_root) {
+ snprintf(newdst, dst_len, "/%s", *p);
+ } else {
+ snprintf(newdst, dst_len, "%s/%s", dstpath, *p);
+ }
+ if (!get_file(afc, testpath, newdst, force_overwrite, recursive_get)) {
+ succeed = 0;
+ break;
+ }
+ free(testpath);
+ free(newdst);
+ p++;
+ }
+ afc_dictionary_free(entries);
+ } else {
+ succeed = get_single_file(afc, srcpath, dstpath, file_size, force_overwrite);
+ }
+ return succeed;
+}
+
+static void handle_get(afc_client_t afc, int argc, char **argv)
+{
+ if (argc < 1) {
+ printf("Error: Invalid number of arguments\n");
+ return;
+ }
+ uint8_t force_overwrite = 0, recursive_get = 0;
+ char *srcpath = NULL;
+ char *dstpath = NULL;
+ int i = 0;
+ for ( ; i < argc; i++) {
+ if (!strcmp(argv[i], "--")) {
+ i++;
+ break;
+ } else if (!strcmp(argv[i], "-r")) {
+ recursive_get = 1;
+ } else if (!strcmp(argv[i], "-f")) {
+ force_overwrite = 1;
+ } else if (!strcmp(argv[i], "-rf") || !strcmp(argv[i], "-fr")) {
+ recursive_get = 1;
+ force_overwrite = 1;
+ } else {
+ break;
+ }
+ }
+ if (argc - i == 1) {
+ char *tmp = strdup(argv[i]);
+ size_t src_len = strlen(tmp);
+ if (src_len > 1 && tmp[src_len - 1] == '/') {
+ tmp[src_len - 1] = '\0';
+ }
+ srcpath = get_absolute_path(tmp);
+ dstpath = strdup(path_get_basename(tmp));
+ free(tmp);
+ } else if (argc - i == 2) {
+ char *tmp = strdup(argv[i]);
+ size_t src_len = strlen(tmp);
+ if (src_len > 1 && tmp[src_len - 1] == '/') {
+ tmp[src_len - 1] = '\0';
+ }
+ srcpath = get_absolute_path(tmp);
+ dstpath = strdup(argv[i + 1]);
+ size_t dst_len = strlen(dstpath);
+ if (dst_len > 1 && dstpath[dst_len - 1] == '/') {
+ dstpath[dst_len - 1] = '\0';
+ }
+ free(tmp);
+ } else {
+ printf("Error: Invalid number of arguments\n");
+ return;
+ }
+
+ // target is a directory, put file under this target
+ if (is_directory(dstpath)) {
+ const char *basen = path_get_basename(argv[0]);
+ uint8_t dst_is_root = strcmp(dstpath, "/") == 0;
+ size_t len = dst_is_root ? (strlen(basen) + 1) : (strlen(dstpath) + 1 + strlen(basen) + 1);
+ char *newdst = (char *) malloc(len);
+ if (dst_is_root) {
+ snprintf(newdst, len, "/%s", basen);
+ } else {
+ snprintf(newdst, len, "%s/%s", dstpath, basen);
+ }
+ get_file(afc, srcpath, newdst, force_overwrite, recursive_get);
+ free(srcpath);
+ free(newdst);
+ free(dstpath);
+ } else {
+ // target is not a dir or does not exist, just try to create or rewrite it
+ get_file(afc, srcpath, dstpath, force_overwrite, recursive_get);
+ free(srcpath);
+ free(dstpath);
+ }
+}
+
+static uint8_t put_single_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint8_t force_overwrite)
+{
+ char **info = NULL;
+ afc_error_t ret = afc_get_file_info(afc, dstpath, &info);
+ // file exists, only overwrite with '-f' option was set
+ if (ret == AFC_E_SUCCESS && info) {
+ afc_dictionary_free(info);
+ if (!force_overwrite) {
+ printf("Error: Failed to write into existing file without '-f' option: %s\n", dstpath);
+ return 0;
+ }
+ }
+ FILE *f = fopen(srcpath, "rb");
+ if (!f) {
+ printf("Error: Failed to open local file '%s': %s\n", srcpath, strerror(errno));
+ return 0;
+ }
+ struct timeval t1;
+ struct timeval t2;
+ struct timeval tdiff;
+ struct stat fst;
+ int progress = 0;
+ size_t bufsize = 0x100000;
+ char *buf = malloc(bufsize);
+
+ fstat(fileno(f), &fst);
+ if (fst.st_size >= 0x400000) {
+ progress = 1;
+ gettimeofday(&t1, NULL);
+ }
+ size_t total = 0;
+ int lastprog = 0;
+ uint64_t fh = 0;
+ afc_error_t err = afc_file_open(afc, dstpath, AFC_FOPEN_RW, &fh);
+ uint8_t succeed = 1;
+ while (err == AFC_E_SUCCESS) {
+ uint32_t bytes_read = fread(buf, 1, bufsize, f);
+ if (bytes_read == 0) {
+ if (!feof(f)) {
+ if (progress) {
+ printf("\n");
+ }
+ printf("Error: Failed to read from local file\n");
+ succeed = 0;
+ }
+ break;
+ }
+ uint32_t chunk = 0;
+ while (chunk < bytes_read) {
+ uint32_t bytes_written = 0;
+ err = afc_file_write(afc, fh, buf + chunk, bytes_read - chunk, &bytes_written);
+ if (err != AFC_E_SUCCESS) {
+ if (progress) {
+ printf("\n");
+ }
+ printf("Error: Failed to write to device file\n");
+ succeed = 0;
+ break;
+ }
+ chunk += bytes_written;
+ }
+ total += chunk;
+ if (progress) {
+ int prog = (int) ((double) total / (double) fst.st_size * 100.0f);
+ if (prog > lastprog) {
+ gettimeofday(&t2, NULL);
+ timeval_subtract(&tdiff, &t2, &t1);
+ double time_in_sec = (double) tdiff.tv_sec + (double) tdiff.tv_usec / 1000000;
+ printf("\r%d%% (%0.1f MB/s) ", prog, (double) total / 1048576.0f / time_in_sec);
+ fflush(stdout);
+ lastprog = prog;
+ }
+ }
+ }
+ free(buf);
+ afc_file_close(afc, fh);
+ fclose(f);
+ return succeed;
+}
+
+static uint8_t put_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint8_t force_overwrite, uint8_t recursive_put)
+{
+ if (is_directory(srcpath)) {
+ if (!recursive_put) {
+ printf("Error: Failed to put directory without '-r' option: %s\n", srcpath);
+ return 0;
+ }
+ char **info = NULL;
+ afc_error_t err = afc_get_file_info(afc, dstpath, &info);
+ //create if target directory does not exist
+ afc_dictionary_free(info);
+ if (err == AFC_E_OBJECT_NOT_FOUND) {
+ err = afc_make_directory(afc, dstpath);
+ if (err != AFC_E_SUCCESS) {
+ printf("Error: Failed to create directory '%s': %s (%d)\n", dstpath, afc_strerror(err), err);
+ return 0;
+ }
+ } else if (!force_overwrite) {
+ printf("Error: Failed to put existing directory without '-f' option: %s\n", dstpath);
+ return 0;
+ }
+ afc_get_file_info(afc, dstpath, &info);
+ uint8_t is_dir = 0;
+ if (info) {
+ char **p = info;
+ while (p && *p) {
+ if (!strcmp(*p, "st_ifmt")) {
+ p++;
+ is_dir = !strcmp(*p, "S_IFDIR");
+ break;
+ }
+ p++;
+ }
+ afc_dictionary_free(info);
+ }
+ if (!is_dir) {
+ printf("Error: Failed to create or access directory: '%s'\n", dstpath);
+ return 0;
+ }
+
+ // walk dir recursively to put files
+ DIR *cur_dir = opendir(srcpath);
+ 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(srcpath, ep->d_name, NULL);
+ if (fpath) {
+ uint8_t dst_is_root = strcmp(dstpath, "/") == 0;
+ size_t len = dst_is_root ? strlen(ep->d_name) + 1 : strlen(dstpath) + 1 + strlen(ep->d_name) + 1;
+ char *newdst = (char *) malloc(len);
+ if (dst_is_root) {
+ snprintf(newdst, len, "/%s", ep->d_name);
+ } else {
+ snprintf(newdst, len, "%s/%s", dstpath, ep->d_name);
+ }
+ if (!put_file(afc, fpath, newdst, force_overwrite, recursive_put)) {
+ free(newdst);
+ free(fpath);
+ return 0;
+ }
+ free(newdst);
+ free(fpath);
+ }
+ }
+ closedir(cur_dir);
+ } else {
+ printf("Error: Failed to visit directory: '%s': %s\n", srcpath, strerror(errno));
+ return 0;
+ }
+ } else {
+ return put_single_file(afc, srcpath, dstpath, force_overwrite);
+ }
+ return 1;
+}
+
+static void handle_put(afc_client_t afc, int argc, char **argv)
+{
+ if (argc < 1) {
+ printf("Error: Invalid number of arguments\n");
+ return;
+ }
+ int i = 0;
+ uint8_t force_overwrite = 0, recursive_put = 0;
+ for ( ; i < argc; i++) {
+ if (!strcmp(argv[i], "--")) {
+ i++;
+ break;
+ } else if (!strcmp(argv[i], "-r")) {
+ recursive_put = 1;
+ } else if (!strcmp(argv[i], "-f")) {
+ force_overwrite = 1;
+ } else if (!strcmp(argv[i], "-rf") || !strcmp(argv[i], "-fr")) {
+ recursive_put = 1;
+ force_overwrite = 1;
+ } else {
+ break;
+ }
+ }
+ if (i >= argc) {
+ printf("Error: Invalid number of arguments\n");
+ return;
+ }
+ char *srcpath = strdup(argv[i]);
+ size_t src_len = strlen(srcpath);
+ if (src_len > 1 && srcpath[src_len - 1] == '/') {
+ srcpath[src_len - 1] = '\0';
+ }
+ char *dstpath = NULL;
+ if (argc - i == 1) {
+ dstpath = get_absolute_path(path_get_basename(srcpath));
+ } else if (argc - i == 2) {
+ char *tmp = strdup(argv[i + 1]);
+ size_t dst_len = strlen(tmp);
+ if (dst_len > 1 && tmp[dst_len - 1] == '/') {
+ tmp[dst_len - 1] = '\0';
+ }
+ dstpath = get_absolute_path(tmp);
+ free(tmp);
+ } else {
+ printf("Error: Invalid number of arguments\n");
+ return;
+ }
+ char **info = NULL;
+ afc_error_t err = afc_get_file_info(afc, dstpath, &info);
+ // target does not exist, put directly
+ if (err == AFC_E_OBJECT_NOT_FOUND) {
+ put_file(afc, srcpath, dstpath, force_overwrite, recursive_put);
+ free(srcpath);
+ free(dstpath);
+ } else {
+ uint8_t is_dir = 0;
+ if (info) {
+ char **p = info;
+ while (p && *p) {
+ if (!strcmp(*p, "st_ifmt")) {
+ p++;
+ is_dir = !strcmp(*p, "S_IFDIR");
+ break;
+ }
+ p++;
+ }
+ afc_dictionary_free(info);
+ }
+ // target is a directory, try to put under this directory
+ if (is_dir) {
+ const char *basen = path_get_basename(srcpath);
+ uint8_t dst_is_root = strcmp(dstpath, "/") == 0;
+ size_t len = dst_is_root ? strlen(basen) + 1 : strlen(dstpath) + 1 + strlen(basen) + 1;
+ char *newdst = (char *) malloc(len);
+ if (dst_is_root) {
+ snprintf(newdst, len, "/%s", basen);
+ } else {
+ snprintf(newdst, len, "%s/%s", dstpath, basen);
+ }
+ free(dstpath);
+ dstpath = get_absolute_path(newdst);
+ free(newdst);
+ put_file(afc, srcpath, dstpath, force_overwrite, recursive_put);
+ } else {
+ //target is common file, rewrite it
+ put_file(afc, srcpath, dstpath, force_overwrite, recursive_put);
+ }
+ free(srcpath);
+ free(dstpath);
+ }
+}
+
+static void handle_pwd(afc_client_t afc, int argc, char** argv)
+{
+ printf("%s\n", curdir);
+}
+
+static void handle_cd(afc_client_t afc, int argc, char** argv)
+{
+ if (argc != 1) {
+ printf("Error: Invalid number of arguments\n");
+ return;
+ }
+
+ if (!strcmp(argv[0], ".")) {
+ return;
+ }
+
+ if (!strcmp(argv[0], "..")) {
+ if (!strcmp(curdir, "/")) {
+ return;
+ }
+ char *p = strrchr(curdir, '/');
+ if (!p) {
+ strcpy(curdir, "/");
+ return;
+ }
+ if (p == curdir) {
+ *(p+1) = '\0';
+ } else {
+ *p = '\0';
+ }
+ return;
+ }
+
+ char* path = get_realpath(argv[0]);
+ int is_dir = 0;
+ char **info = NULL;
+ afc_error_t err = afc_get_file_info(afc, path, &info);
+ if (err == AFC_E_SUCCESS && info) {
+ int i;
+ for (i = 0; info[i]; i += 2) {
+ if (!strcmp(info[i], "st_ifmt")) {
+ if (!strcmp(info[i+1], "S_IFDIR")) {
+ is_dir = 1;
+ }
+ break;
+ }
+ }
+ afc_dictionary_free(info);
+ } else {
+ printf("Error: Failed to get file info for %s: %s (%d)\n", path, afc_strerror(err), err);
+ free(path);
+ return;
+ }
+
+ if (!is_dir) {
+ printf("Error: '%s' is not a valid directory\n", path);
+ free(path);
+ return;
+ }
+
+ free(curdir);
+ curdir = path;
+ curdir_len = strlen(curdir);
+}
+
+static void parse_cmdline(int* p_argc, char*** p_argv, const char* cmdline)
+{
+ char **argv = NULL;
+ int argc = 0;
+ size_t maxlen = strlen(cmdline);
+ const char* pos = cmdline;
+ const char* qpos = NULL;
+ char *tmpbuf = NULL;
+ int tmplen = 0;
+ int is_error = 0;
+
+ /* skip initial whitespace */
+ while (isspace(*pos)) pos++;
+ maxlen -= (pos - cmdline);
+
+ tmpbuf = (char*)malloc(maxlen+1);
+
+ while (!is_error) {
+ if (*pos == '\\') {
+ pos++;
+ switch (*pos) {
+ case '"':
+ case '\'':
+ case '\\':
+ case ' ':
+ tmpbuf[tmplen++] = *pos;
+ pos++;
+ break;
+ default:
+ printf("Error: Invalid escape sequence\n");
+ is_error++;
+ break;
+ }
+ } else if (*pos == '"' || *pos == '\'') {
+ if (!qpos) {
+ qpos = pos;
+ } else {
+ qpos = NULL;
+ }
+ pos++;
+ } else if (*pos == '\0' || (!qpos && isspace(*pos))) {
+ tmpbuf[tmplen] = '\0';
+ if (*pos == '\0' && qpos) {
+ printf("Error: Unmatched `%c`\n", *qpos);
+ is_error++;
+ break;
+ }
+ char** new_argv = (char**)realloc(argv, (argc+1)*sizeof(char*));
+ if (new_argv == NULL) {
+ printf("Error: Out of memory?!\n");
+ is_error++;
+ break;
+ }
+ argv = new_argv;
+ /* shrink buffer to actual argument size */
+ argv[argc] = (char*)realloc(tmpbuf, tmplen+1);
+ if (!argv[argc]) {
+ printf("Error: Out of memory?!\n");
+ is_error++;
+ break;
+ }
+ argc++;
+ tmpbuf = NULL;
+ if (*pos == '\0') {
+ break;
+ }
+ maxlen -= tmplen;
+ tmpbuf = (char*)malloc(maxlen+1);
+ tmplen = 0;
+ while (isspace(*pos)) pos++;
+ } else {
+ tmpbuf[tmplen++] = *pos;
+ pos++;
+ }
+ }
+ if (tmpbuf) {
+ free(tmpbuf);
+ }
+ if (is_error) {
+ int i;
+ for (i = 0; argv && i < argc; i++) free(argv[i]);
+ free(argv);
+ return;
+ }
+
+ *p_argv = argv;
+ *p_argc = argc;
+}
+
+static int process_args(afc_client_t afc, int argc, char** argv)
+{
+ if (!strcmp(argv[0], "q") || !strcmp(argv[0], "quit") || !strcmp(argv[0], "exit")) {
+ return -1;
+ }
+ else if (!strcmp(argv[0], "help")) {
+ handle_help(afc, argc, argv);
+ }
+ else if (!strcmp(argv[0], "devinfo") || !strcmp(argv[0], "deviceinfo")) {
+ handle_devinfo(afc, argc-1, argv+1);
+ }
+ else if (!strcmp(argv[0], "info")) {
+ handle_file_info(afc, argc-1, argv+1);
+ }
+ else if (!strcmp(argv[0], "ls") || !strcmp(argv[0], "list")) {
+ handle_list(afc, argc-1, argv+1);
+ }
+ else if (!strcmp(argv[0], "mv") || !strcmp(argv[0], "rename")) {
+ handle_rename(afc, argc-1, argv+1);
+ }
+ else if (!strcmp(argv[0], "mkdir")) {
+ handle_mkdir(afc, argc-1, argv+1);
+ }
+ else if (!strcmp(argv[0], "ln")) {
+ handle_link(afc, argc-1, argv+1);
+ }
+ else if (!strcmp(argv[0], "rm") || !strcmp(argv[0], "remove")) {
+ handle_remove(afc, argc-1, argv+1);
+ }
+ else if (!strcmp(argv[0], "get")) {
+ handle_get(afc, argc-1, argv+1);
+ }
+ else if (!strcmp(argv[0], "put")) {
+ handle_put(afc, argc-1, argv+1);
+ }
+ else if (!strcmp(argv[0], "pwd")) {
+ handle_pwd(afc, argc-1, argv+1);
+ }
+ else if (!strcmp(argv[0], "cd")) {
+ handle_cd(afc, argc-1, argv+1);
+ }
+ else {
+ printf("Unknown command '%s'. Type 'help' to get a list of available commands.\n", argv[0]);
+ }
+ return 0;
+}
+
+static void start_cmdline(afc_client_t afc)
+{
+ while (!stop_requested) {
+ int argc = 0;
+ char **argv = NULL;
+ char prompt[128];
+ int plen = curdir_len;
+ char *ppath = curdir;
+ int plim = (int)(sizeof(prompt)/2)-8;
+ if (plen > plim) {
+ ppath = curdir + (plen - plim);
+ plen = plim;
+ }
+ snprintf(prompt, 128, FG_BLACK BG_LIGHT_GRAY "afc:" COLOR_RESET FG_BRIGHT_YELLOW BG_BLUE "%.*s" COLOR_RESET " > ", plen, ppath);
+#ifdef HAVE_READLINE
+ char* cmd = readline(prompt);
+ if (!cmd || !*cmd) {
+ free(cmd);
+ continue;
+ }
+ add_history(cmd);
+ parse_cmdline(&argc, &argv, cmd);
+#else
+ char cmdbuf[4096];
+ printf("%s", prompt);
+ fflush(stdout);
+ get_input(cmdbuf, sizeof(cmdbuf));
+ parse_cmdline(&argc, &argv, cmdbuf);
+#endif
+#ifdef HAVE_READLINE
+ free(cmd);
+#endif
+ /* process arguments */
+ if (argv && argv[0]) {
+ if (process_args(afc, argc, argv) < 0) {
+ break;
+ }
+ }
+ }
+}
+
+static void device_event_cb(const idevice_event_t* event, void* userdata)
+{
+ if (use_network && event->conn_type != CONNECTION_NETWORK) {
+ return;
+ } else if (!use_network && event->conn_type != CONNECTION_USBMUXD) {
+ return;
+ }
+ if (event->event == IDEVICE_DEVICE_ADD) {
+ if (!udid) {
+ udid = strdup(event->udid);
+ }
+ if (strcmp(udid, event->udid) == 0) {
+ connected = 1;
+ }
+ } else if (event->event == IDEVICE_DEVICE_REMOVE) {
+ if (strcmp(udid, event->udid) == 0) {
+ connected = 0;
+ printf("\n[disconnected]\n");
+ handle_signal(SIGINT);
+ }
+ }
+}
+
+int main(int argc, char** argv)
+{
+ const char* appid = NULL;
+ int ret = 0;
+ idevice_t device = NULL;
+ lockdownd_client_t lockdown = NULL;
+ lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR;
+ lockdownd_service_descriptor_t service = NULL;
+ afc_client_t afc = NULL;
+ house_arrest_client_t house_arrest = NULL;
+ const char* service_name = AFC_SERVICE_NAME;
+ int use_container = 0;
+
+ int c = 0;
+ const struct option longopts[] = {
+ { "udid", required_argument, NULL, 'u' },
+ { "network", no_argument, NULL, 'n' },
+ { "help", no_argument, NULL, 'h' },
+ { "debug", no_argument, NULL, 'd' },
+ { "version", no_argument, NULL, 'v' },
+ { "documents", required_argument, NULL, OPT_DOCUMENTS },
+ { "container", required_argument, NULL, OPT_CONTAINER },
+ { NULL, 0, NULL, 0}
+ };
+
+ signal(SIGTERM, handle_signal);
+#ifndef WIN32
+ signal(SIGQUIT, handle_signal);
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ while ((c = getopt_long(argc, argv, "du:nhv", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ idevice_set_debug_level(1);
+ break;
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ udid = strdup(optarg);
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
+ return 0;
+ case 'v':
+ printf("%s %s", TOOL_NAME, PACKAGE_VERSION);
+#ifdef HAVE_READLINE
+ printf(" (readline)");
+#endif
+ printf("\n");
+ return 0;
+ case OPT_DOCUMENTS:
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: '--documents' requires a non-empty app ID!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ appid = optarg;
+ use_container = 0;
+ break;
+ case OPT_CONTAINER:
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: '--container' requires a not-empty app ID!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ appid = optarg;
+ use_container = 1;
+ break;
+ default:
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ int num = 0;
+ idevice_info_t *devices = NULL;
+ idevice_get_device_list_extended(&devices, &num);
+ int count = 0;
+ for (int i = 0; i < num; i++) {
+ if (devices[i]->conn_type == CONNECTION_NETWORK && use_network) {
+ count++;
+ } else if (devices[i]->conn_type == CONNECTION_USBMUXD) {
+ count++;
+ }
+ }
+ idevice_device_list_extended_free(devices);
+ if (count == 0) {
+ fprintf(stderr, "No device found. Plug in a device or pass UDID with -u to wait for device to be available.\n");
+ return 1;
+ }
+
+ idevice_events_subscribe(&context, device_event_cb, NULL);
+
+ while (!connected && !stop_requested) {
+#ifdef WIN32
+ Sleep(100);
+#else
+ usleep(100000);
+#endif
+ }
+ if (stop_requested) {
+ return 0;
+ }
+
+ ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX);
+ if (ret != IDEVICE_E_SUCCESS) {
+ if (udid) {
+ fprintf(stderr, "ERROR: Device %s not found!\n", udid);
+ } else {
+ fprintf(stderr, "ERROR: No device found!\n");
+ }
+ return 1;
+ }
+
+ do {
+ if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) {
+ fprintf(stderr, "ERROR: Could not connect to lockdownd: %s (%d)\n", lockdownd_strerror(ldret), ldret);
+ ret = 1;
+ break;
+ }
+
+ if (appid) {
+ service_name = HOUSE_ARREST_SERVICE_NAME;
+ }
+
+ ldret = lockdownd_start_service(lockdown, service_name, &service);
+ if (ldret != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "ERROR: Failed to start service %s: %s (%d)\n", service_name, lockdownd_strerror(ldret), ldret);
+ ret = 1;
+ break;
+ }
+
+ if (appid) {
+ house_arrest_client_new(device, service, &house_arrest);
+ if (!house_arrest) {
+ fprintf(stderr, "Could not start document sharing service!\n");
+ ret = 1;
+ break;
+ }
+
+ if (house_arrest_send_command(house_arrest, use_container ? "VendContainer": "VendDocuments", appid) != HOUSE_ARREST_E_SUCCESS) {
+ fprintf(stderr, "Could not send house_arrest command!\n");
+ ret = 1;
+ break;
+ }
+
+ plist_t dict = NULL;
+ if (house_arrest_get_result(house_arrest, &dict) != HOUSE_ARREST_E_SUCCESS) {
+ fprintf(stderr, "Could not get result from document sharing service!\n");
+ break;
+ }
+ plist_t node = plist_dict_get_item(dict, "Error");
+ if (node) {
+ char *str = NULL;
+ plist_get_string_val(node, &str);
+ fprintf(stderr, "ERROR: %s\n", str);
+ if (str && !strcmp(str, "InstallationLookupFailed")) {
+ fprintf(stderr, "The App '%s' is either not present on the device, or the 'UIFileSharingEnabled' key is not set in its Info.plist. Starting with iOS 8.3 this key is mandatory to allow access to an app's Documents folder.\n", appid);
+ }
+ free(str);
+ plist_free(dict);
+ break;
+ }
+ plist_free(dict);
+ afc_client_new_from_house_arrest_client(house_arrest, &afc);
+ } else {
+ afc_client_new(device, service, &afc);
+ }
+ lockdownd_service_descriptor_free(service);
+ lockdownd_client_free(lockdown);
+ lockdown = NULL;
+
+ curdir = strdup("/");
+ curdir_len = 1;
+
+ if (argc > 0) {
+ // command line mode
+ process_args(afc, argc, argv);
+ } else {
+ // interactive mode
+ start_cmdline(afc);
+ }
+
+ } while (0);
+
+ if (afc) {
+ afc_client_free(afc);
+ }
+ if (lockdown) {
+ lockdownd_client_free(lockdown);
+ }
+ idevice_free(device);
+
+ return ret;
+}
diff --git a/tools/idevice_id.c b/tools/idevice_id.c
index 1facb60..540a6f2 100644
--- a/tools/idevice_id.c
+++ b/tools/idevice_id.c
@@ -1,6 +1,34 @@
+/*
+ * idevice_id.c
+ * Prints device name or a list of attached devices
+ *
+ * Copyright (C) 2010-2018 Nikias Bassen <nikias@gmx.li>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "idevice_id"
+
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
+#include <getopt.h>
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/lockdown.h>
@@ -8,100 +36,139 @@
#define MODE_SHOW_ID 1
#define MODE_LIST_DEVICES 2
-static void print_usage(int argc, char **argv)
+static void print_usage(int argc, char **argv, int is_error)
{
- char *name = NULL;
-
- name = strrchr(argv[0], '/');
- printf("Usage: %s [OPTIONS] [UUID]\n", (name ? name + 1: argv[0]));
- printf("Prints device name or a list of attached iPhone/iPod Touch devices.\n\n");
- printf(" The UUID is a 40-digit hexadecimal number of the device\n");
- printf(" for which the name should be retrieved.\n\n");
- printf(" -l, --list\t\tlist UUID of all attached devices\n");
- printf(" -d, --debug\t\tenable communication debugging\n");
- printf(" -h, --help\t\tprints usage information\n");
- printf("\n");
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] [UDID]\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "List attached devices or print device name of given device.\n"
+ "\n" \
+ " If UDID is given, the name of the connected device with that UDID"
+ " will be retrieved.\n"
+ "\n" \
+ "OPTIONS:\n"
+ " -l, --list list UDIDs of all devices attached via USB\n"
+ " -n, --network list UDIDs of all devices available via network\n"
+ " -d, --debug enable communication debugging\n"
+ " -h, --help prints usage information\n"
+ " -v, --version prints version information\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
}
int main(int argc, char **argv)
{
- idevice_t phone = NULL;
+ idevice_t device = NULL;
lockdownd_client_t client = NULL;
- char **dev_list = NULL;
- char *devname = NULL;
+ idevice_info_t *dev_list = NULL;
+ char *device_name = NULL;
int ret = 0;
int i;
- int mode = MODE_SHOW_ID;
- char uuid[41];
- uuid[0] = 0;
+ int mode = MODE_LIST_DEVICES;
+ int include_usb = 0;
+ int include_network = 0;
+ const char* udid = NULL;
- /* parse cmdline args */
- for (i = 1; i < argc; i++) {
- if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) {
+ int c = 0;
+ const struct option longopts[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "list", no_argument, NULL, 'l' },
+ { "network", no_argument, NULL, 'n' },
+ { "version", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0}
+ };
+
+ while ((c = getopt_long(argc, argv, "dhlnv", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
idevice_set_debug_level(1);
- continue;
- }
- else if (!strcmp(argv[i], "-l") || !strcmp(argv[i], "--list")) {
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
+ exit(EXIT_SUCCESS);
+ case 'l':
mode = MODE_LIST_DEVICES;
- continue;
- }
- else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
- print_usage(argc, argv);
+ include_usb = 1;
+ break;
+ case 'n':
+ mode = MODE_LIST_DEVICES;
+ include_network = 1;
+ break;
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
return 0;
+ default:
+ print_usage(argc, argv, 1);
+ exit(EXIT_FAILURE);
}
}
+ argc -= optind;
+ argv += optind;
- /* check if uuid was passed */
- if (mode == MODE_SHOW_ID) {
- i--;
- if (!argv[i] || (strlen(argv[i]) != 40)) {
- print_usage(argc, argv);
- return 0;
- }
- strcpy(uuid, argv[i]);
+ if (argc == 1) {
+ mode = MODE_SHOW_ID;
+ } else if (argc == 0 && optind == 1) {
+ include_usb = 1;
+ include_network = 1;
}
+ udid = argv[0];
switch (mode) {
case MODE_SHOW_ID:
- idevice_new(&phone, uuid);
- if (!phone) {
- fprintf(stderr, "ERROR: No device with UUID=%s attached.\n", uuid);
+ idevice_new_with_options(&device, udid, IDEVICE_LOOKUP_USBMUX | IDEVICE_LOOKUP_NETWORK);
+ if (!device) {
+ fprintf(stderr, "ERROR: No device with UDID %s attached.\n", udid);
return -2;
}
- if (LOCKDOWN_E_SUCCESS != lockdownd_client_new(phone, &client, "idevice_id")) {
- idevice_free(phone);
+ if (LOCKDOWN_E_SUCCESS != lockdownd_client_new(device, &client, TOOL_NAME)) {
+ idevice_free(device);
fprintf(stderr, "ERROR: Connecting to device failed!\n");
return -2;
}
- if ((LOCKDOWN_E_SUCCESS != lockdownd_get_device_name(client, &devname)) || !devname) {
+ if ((LOCKDOWN_E_SUCCESS != lockdownd_get_device_name(client, &device_name)) || !device_name) {
fprintf(stderr, "ERROR: Could not get device name!\n");
ret = -2;
}
lockdownd_client_free(client);
- idevice_free(phone);
+ idevice_free(device);
if (ret == 0) {
- printf("%s\n", devname);
+ printf("%s\n", device_name);
}
- if (devname) {
- free(devname);
+ if (device_name) {
+ free(device_name);
}
+ break;
- return ret;
case MODE_LIST_DEVICES:
default:
- if (idevice_get_device_list(&dev_list, &i) < 0) {
+ if (idevice_get_device_list_extended(&dev_list, &i) < 0) {
fprintf(stderr, "ERROR: Unable to retrieve device list!\n");
return -1;
}
for (i = 0; dev_list[i] != NULL; i++) {
- printf("%s\n", dev_list[i]);
+ if (dev_list[i]->conn_type == CONNECTION_USBMUXD && !include_usb) continue;
+ if (dev_list[i]->conn_type == CONNECTION_NETWORK && !include_network) continue;
+ printf("%s", dev_list[i]->udid);
+ if (include_usb && include_network) {
+ if (dev_list[i]->conn_type == CONNECTION_NETWORK) {
+ printf(" (Network)");
+ } else {
+ printf(" (USB)");
+ }
+ }
+ printf("\n");
}
- idevice_device_list_free(dev_list);
- return 0;
+ idevice_device_list_extended_free(dev_list);
+ break;
}
+ return ret;
}
diff --git a/tools/idevicebackup.c b/tools/idevicebackup.c
index 867eaad..c0537b8 100644
--- a/tools/idevicebackup.c
+++ b/tools/idevicebackup.c
@@ -9,31 +9,41 @@
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "idevicebackup"
+
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
-#include <glib.h>
-#include <gcrypt.h>
+#include <getopt.h>
#include <unistd.h>
+#include <ctype.h>
+#include <time.h>
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/lockdown.h>
#include <libimobiledevice/mobilebackup.h>
#include <libimobiledevice/notification_proxy.h>
#include <libimobiledevice/afc.h>
+#include <libimobiledevice-glue/sha.h>
+#include <libimobiledevice-glue/utils.h>
+#include <plist/plist.h>
#define MOBILEBACKUP_SERVICE_NAME "com.apple.mobilebackup"
#define NP_SERVICE_NAME "com.apple.mobile.notification_proxy"
@@ -41,9 +51,14 @@
#define LOCK_ATTEMPTS 50
#define LOCK_WAIT 200000
+#ifdef WIN32
+#include <windows.h>
+#define sleep(x) Sleep(x*1000)
+#endif
+
static mobilebackup_client_t mobilebackup = NULL;
static lockdownd_client_t client = NULL;
-static idevice_t phone = NULL;
+static idevice_t device = NULL;
static int quit_flag = 0;
@@ -53,22 +68,12 @@ enum cmd_mode {
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;
@@ -82,51 +87,47 @@ static int compare_hash(const unsigned char *hash1, const unsigned char *hash2,
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);
-
+ sha1_context sha1;
+ sha1_init(&sha1);
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);
+ sha1_update(&sha1, buf, len);
}
fclose(f);
- gcry_md_write(hd, destpath, strlen(destpath));
- gcry_md_write(hd, ";", 1);
+ sha1_update(&sha1, destpath, strlen(destpath));
+ sha1_update(&sha1, ";", 1);
+
if (greylist == 1) {
- gcry_md_write(hd, "true", 4);
+ sha1_update(&sha1, "true", 4);
} else {
- gcry_md_write(hd, "false", 5);
+ sha1_update(&sha1, "false", 5);
}
- gcry_md_write(hd, ";", 1);
+ sha1_update(&sha1, ";", 1);
+
if (domain) {
- gcry_md_write(hd, domain, strlen(domain));
+ sha1_update(&sha1, domain, strlen(domain));
} else {
- gcry_md_write(hd, "(null)", 6);
+ sha1_update(&sha1, "(null)", 6);
}
- gcry_md_write(hd, ";", 1);
+ sha1_update(&sha1, ";", 1);
+
if (appid) {
- gcry_md_write(hd, appid, strlen(appid));
+ sha1_update(&sha1, appid, strlen(appid));
} else {
- gcry_md_write(hd, "(null)", 6);
+ sha1_update(&sha1, "(null)", 6);
}
- gcry_md_write(hd, ";", 1);
+ sha1_update(&sha1, ";", 1);
+
if (version) {
- gcry_md_write(hd, version, strlen(version));
+ sha1_update(&sha1, version, strlen(version));
} else {
- gcry_md_write(hd, "(null)", 6);
+ sha1_update(&sha1, "(null)", 6);
}
- unsigned char *newhash = gcry_md_read(hd, GCRY_MD_SHA1);
- memcpy(hash_out, newhash, 20);
+ sha1_final(&sha1, hash_out);
}
- gcry_md_close(hd);
}
static void print_hash(const unsigned char *hash, int len)
@@ -147,14 +148,12 @@ static void notify_cb(const char *notification, void *userdata)
}
}
-static plist_t mobilebackup_factory_info_plist_new()
+static plist_t mobilebackup_factory_info_plist_new(const char* udid)
{
/* 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;
+ char *udid_uppercase = NULL;
plist_t ret = plist_new_dict();
@@ -163,45 +162,42 @@ static plist_t mobilebackup_factory_info_plist_new()
/* set fields we understand */
value_node = plist_dict_get_item(root_node, "BuildVersion");
- plist_dict_insert_item(ret, "Build Version", plist_copy(value_node));
+ plist_dict_set_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));
+ plist_dict_set_item(ret, "Device Name", plist_copy(value_node));
+ plist_dict_set_item(ret, "Display Name", plist_copy(value_node));
/* FIXME: How is the GUID generated? */
- plist_dict_insert_item(ret, "GUID", plist_new_string("---"));
+ plist_dict_set_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));
+ plist_dict_set_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));
+ plist_dict_set_item(ret, "Last Backup Date", plist_new_date(time(NULL) - MAC_EPOCH, 0));
value_node = plist_dict_get_item(root_node, "ProductType");
- plist_dict_insert_item(ret, "Product Type", plist_copy(value_node));
+ plist_dict_set_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));
+ plist_dict_set_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));
+ plist_dict_set_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));
+ plist_dict_set_item(ret, "Target Identifier", plist_new_string(udid));
/* 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);
+ udid_uppercase = string_toupper((char*)udid);
+ plist_dict_set_item(ret, "Unique Identifier", plist_new_string(udid_uppercase));
+ free(udid_uppercase);
/* FIXME: Embed files as <data> nodes */
plist_t files = plist_new_dict();
- plist_dict_insert_item(ret, "iTunes Files", files);
- plist_dict_insert_item(ret, "iTunes Version", plist_new_string("9.0.2"));
+ plist_dict_set_item(ret, "iTunes Files", files);
+ plist_dict_set_item(ret, "iTunes Version", plist_new_string("9.0.2"));
plist_free(root_node);
@@ -210,102 +206,17 @@ static plist_t mobilebackup_factory_info_plist_new()
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);
+ plist_set_date_val(node, time(NULL) - MAC_EPOCH, 0);
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");
- 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;
@@ -321,11 +232,14 @@ static int plist_strcmp(plist_t node, const char *str)
return ret;
}
-static gchar *mobilebackup_build_path(const char *backup_directory, const char *name, const char *extension)
+static char *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);
+ char* filename = (char*)malloc(strlen(name)+(extension == NULL ? 0: strlen(extension))+1);
+ strcpy(filename, name);
+ if (extension != NULL)
+ strcat(filename, extension);
+ char *path = string_build_path(backup_directory, filename, NULL);
+ free(filename);
return path;
}
@@ -333,28 +247,28 @@ 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");
+ plist_dict_set_item(status_plist, "Backup Success", plist_new_bool(status));
+ char *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_write_to_file(status_plist, file_path, PLIST_FORMAT_XML, 0);
plist_free(status_plist);
status_plist = NULL;
- g_free(file_path);
+ 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");
+ char *file_path = mobilebackup_build_path(path, "Status", ".plist");
- plist_read_from_filename(&status_plist, file_path);
- g_free(file_path);
+ plist_read_from_file(file_path, &status_plist, NULL);
+ free(file_path);
if (!status_plist) {
printf("Could not read Status.plist!\n");
return ret;
@@ -387,7 +301,7 @@ static int mobilebackup_info_is_current_device(plist_t info)
/* get basic device information in one go */
lockdownd_get_value(client, NULL, NULL, &root_node);
- /* verify UUID */
+ /* verify UDID */
value_node = plist_dict_get_item(root_node, "UniqueDeviceID");
node = plist_dict_get_item(info, "Target Identifier");
@@ -435,14 +349,14 @@ static int mobilebackup_info_is_current_device(plist_t info)
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");
+ char *path = mobilebackup_build_path(backup_directory, hash, ".mddata");
printf("Removing \"%s\" ", path);
if (!remove( path ))
ret = 1;
else
ret = 0;
- g_free(path);
+ free(path);
if (!ret)
return ret;
@@ -454,7 +368,7 @@ static int mobilebackup_delete_backup_file_by_hash(const char *backup_directory,
else
ret = 0;
- g_free(path);
+ free(path);
return ret;
}
@@ -476,7 +390,7 @@ static int mobilebackup_check_file_integrity(const char *backup_directory, const
}
infopath = mobilebackup_build_path(backup_directory, hash, ".mdinfo");
- plist_read_from_filename(&mdinfo, infopath);
+ plist_read_from_file(infopath, &mdinfo, NULL);
free(infopath);
if (!mdinfo) {
printf("\r\n");
@@ -521,13 +435,13 @@ static int mobilebackup_check_file_integrity(const char *backup_directory, const
char *version = NULL;
node = plist_dict_get_item(metadata, "Version");
- if (node && (plist_get_node_type(node) == PLIST_STRING)) {
+ 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)) {
+ if (node && (plist_get_node_type(node) == PLIST_STRING)) {
plist_get_string_val(node, &destpath);
}
@@ -539,7 +453,7 @@ static int mobilebackup_check_file_integrity(const char *backup_directory, const
char *domain = NULL;
node = plist_dict_get_item(metadata, "Domain");
- if (node && (plist_get_node_type(node) == PLIST_STRING)) {
+ if (node && (plist_get_node_type(node) == PLIST_STRING)) {
plist_get_string_val(node, &domain);
}
@@ -550,14 +464,14 @@ static int mobilebackup_check_file_integrity(const char *backup_directory, const
unsigned char fnhash[20];
char fnamehash[41];
char *p = fnamehash;
- sha1_of_data(fnstr, strlen(fnstr), fnhash);
+ sha1((const unsigned char*)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");
+ if (strcmp(fnamehash, hash) != 0) {
+ printf("\r\n");
printf("WARNING: filename hash does not match for entry '%s'\n", hash);
}
@@ -567,7 +481,7 @@ static int mobilebackup_check_file_integrity(const char *backup_directory, const
plist_get_string_val(node, &auth_version);
}
- if (strcmp(auth_version, "1.0")) {
+ if (strcmp(auth_version, "1.0") != 0) {
printf("\r\n");
printf("WARNING: Unknown AuthVersion '%s', DataHash cannot be verified!\n", auth_version);
}
@@ -591,9 +505,9 @@ static int mobilebackup_check_file_integrity(const char *backup_directory, const
hash_ok = 1;
}
- g_free(domain);
- g_free(version);
- g_free(destpath);
+ free(domain);
+ free(version);
+ free(destpath);
if (!hash_ok) {
printf("\r\n");
@@ -605,31 +519,36 @@ static int mobilebackup_check_file_integrity(const char *backup_directory, const
printf("\n");
res = 0;
}
- g_free(data_hash);
+ free(data_hash);
plist_free(mdinfo);
return res;
}
static void do_post_notification(const char *notification)
{
- uint16_t nport = 0;
+ lockdownd_service_descriptor_t service = NULL;
np_client_t np;
if (!client) {
- if (lockdownd_client_new_with_handshake(phone, &client, "idevicebackup") != LOCKDOWN_E_SUCCESS) {
+ if (lockdownd_client_new_with_handshake(device, &client, TOOL_NAME) != LOCKDOWN_E_SUCCESS) {
return;
}
}
- lockdownd_start_service(client, NP_SERVICE_NAME, &nport);
- if (nport) {
- np_client_new(phone, nport, &np);
+ lockdownd_error_t ldret = lockdownd_start_service(client, NP_SERVICE_NAME, &service);
+ if (ldret == LOCKDOWN_E_SUCCESS) {
+ np_client_new(device, service, &np);
if (np) {
np_post_notification(np, notification);
np_client_free(np);
}
} else {
- printf("Could not start %s\n", NP_SERVICE_NAME);
+ printf("Could not start %s: %s\n", NP_SERVICE_NAME, lockdownd_strerror(ldret));
+ }
+
+ if (service) {
+ lockdownd_service_descriptor_free(service);
+ service = NULL;
}
}
@@ -665,29 +584,38 @@ static void clean_exit(int sig)
quit_flag++;
}
-static void print_usage(int argc, char **argv)
+static void print_usage(int argc, char **argv, int is_error)
{
- 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");
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] CMD DIRECTORY\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Create or restore backup in/from the specified directory.\n"
+ "\n"
+ "CMD:\n"
+ " backup Saves a device backup into DIRECTORY\n"
+ " restore Restores a device backup from DIRECTORY.\n"
+ "\n"
+ "OPTIONS:\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ " -n, --network connect to network device\n"
+ " -d, --debug enable communication debugging\n"
+ " -h, --help prints usage information\n"
+ " -v, --version prints version information\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
}
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 uuid[41];
- uint16_t port = 0;
- uuid[0] = 0;
+ char* udid = NULL;
+ int use_network = 0;
+ lockdownd_service_descriptor_t service = NULL;
int cmd = -1;
int is_full_backup = 0;
char *backup_directory = NULL;
@@ -701,60 +629,77 @@ int main(int argc, char *argv[])
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;
+ int c = 0;
+ const struct option longopts[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+ { "network", no_argument, NULL, 'n' },
+ { "version", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0}
+ };
/* 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);
+#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")) {
+ while ((c = getopt_long(argc, argv, "dhu:nv", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
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;
+ break;
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
}
- strcpy(uuid, argv[i]);
- continue;
- }
- else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
- print_usage(argc, argv);
+ udid = strdup(optarg);
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
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);
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
return 0;
+ default:
+ print_usage(argc, argv, 1);
+ return 2;
}
}
+ argc -= optind;
+ argv += optind;
- /* verify options */
- if (cmd == -1) {
- printf("No command specified.\n");
- print_usage(argc, argv);
- return -1;
+ if (argc < 1) {
+ fprintf(stderr, "ERROR: Missing command.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
}
- if (backup_directory == NULL) {
- printf("No target backup directory specified.\n");
- print_usage(argc, argv);
- return -1;
+ if (!strcmp(argv[0], "backup")) {
+ cmd = CMD_BACKUP;
+ } else if (!strcmp(argv[0], "restore")) {
+ cmd = CMD_RESTORE;
+ } else {
+ fprintf(stderr, "ERROR: Invalid command '%s'.\n", argv[0]);
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+
+ if (argc < 2) {
+ fprintf(stderr, "No target backup directory specified.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
}
+ backup_directory = argv[1];
/* verify if passed backup directory exists */
if (stat(backup_directory, &st) != 0) {
@@ -766,7 +711,7 @@ int main(int argc, char *argv[])
char *info_path = mobilebackup_build_path(backup_directory, "Info", ".plist");
if (cmd == CMD_RESTORE) {
if (stat(info_path, &st) != 0) {
- g_free(info_path);
+ free(info_path);
printf("ERROR: Backup directory \"%s\" is invalid. No Info.plist found.\n", backup_directory);
return -1;
}
@@ -774,32 +719,54 @@ int main(int argc, char *argv[])
printf("Backup directory is \"%s\"\n", backup_directory);
- 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;
+ 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;
}
- else
- {
- ret = idevice_new(&phone, NULL);
- if (ret != IDEVICE_E_SUCCESS) {
- printf("No device found, is it plugged in?\n");
- return -1;
- }
+
+ if (!udid) {
+ idevice_get_udid(device, &udid);
}
- if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "idevicebackup")) {
- idevice_free(phone);
+ if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &client, TOOL_NAME))) {
+ printf("ERROR: Could not connect to lockdownd, error code %d\n", ldret);
+ idevice_free(device);
+ free(udid);
return -1;
}
+ node = NULL;
+ lockdownd_get_value(client, NULL, "ProductVersion", &node);
+ if (node) {
+ char* str = NULL;
+ if (plist_get_node_type(node) == PLIST_STRING) {
+ plist_get_string_val(node, &str);
+ }
+ plist_free(node);
+ node = NULL;
+ if (str) {
+ int maj = strtol(str, NULL, 10);
+ free(str);
+ if (maj > 3) {
+ printf("ERROR: This tool is only compatible with iOS 3 or below. For newer iOS versions please use the idevicebackup2 tool.\n");
+ lockdownd_client_free(client);
+ idevice_free(device);
+ free(udid);
+ 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);
+ ldret = lockdownd_start_service(client, 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,
@@ -810,25 +777,37 @@ int main(int argc, char *argv[])
};
np_observe_notifications(np, noties);
} else {
- printf("ERROR: Could not start service %s.\n", NP_SERVICE_NAME);
+ printf("ERROR: Could not start service %s: %s\n", NP_SERVICE_NAME, lockdownd_strerror(ldret));
}
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);
+ service->port = 0;
+ service->ssl_enabled = 0;
+ ldret = lockdownd_start_service(client, 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));
}
}
+ if (service) {
+ lockdownd_service_descriptor_free(service);
+ service = NULL;
+ }
+
/* start mobilebackup service and retrieve port */
- port = 0;
- ret = lockdownd_start_service(client, MOBILEBACKUP_SERVICE_NAME, &port);
- if ((ret == LOCKDOWN_E_SUCCESS) && port) {
- printf("Started \"%s\" service on port %d.\n", MOBILEBACKUP_SERVICE_NAME, port);
- mobilebackup_client_new(phone, port, &mobilebackup);
+ ldret = lockdownd_start_service(client, MOBILEBACKUP_SERVICE_NAME, &service);
+ if ((ldret == LOCKDOWN_E_SUCCESS) && service && service->port) {
+ printf("Started \"%s\" service on port %d.\n", MOBILEBACKUP_SERVICE_NAME, service->port);
+ printf("%d\n", mobilebackup_client_new(device, service, &mobilebackup));
+
+ if (service) {
+ lockdownd_service_descriptor_free(service);
+ service = NULL;
+ }
/* check abort conditions */
if (quit_flag > 0) {
@@ -839,7 +818,7 @@ int main(int argc, char *argv[])
/* 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);
+ plist_read_from_file(info_path, &info_plist, NULL);
if (!info_plist) {
printf("Could not read Info.plist\n");
@@ -850,7 +829,7 @@ int main(int argc, char *argv[])
/* 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);
+ plist_write_to_file(info_plist, info_path, PLIST_FORMAT_XML, 0);
} else {
printf("Aborting backup. Backup is not compatible with the current device.\n");
cmd = CMD_LEAVE;
@@ -883,15 +862,16 @@ int main(int argc, char *argv[])
if (aerr == AFC_E_SUCCESS) {
do_post_notification(NP_SYNC_DID_START);
break;
- } else if (aerr == AFC_E_OP_WOULD_BLOCK) {
+ }
+ 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;
}
+
+ 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");
@@ -910,12 +890,12 @@ int main(int argc, char *argv[])
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 */
+ /* TODO: verify battery on AC enough battery remaining */
/* read the last Manifest.plist */
if (!is_full_backup) {
printf("Reading existing Manifest.\n");
- plist_read_from_filename(&manifest_plist, manifest_path);
+ plist_read_from_file(manifest_path, &manifest_plist, NULL);
if (!manifest_plist) {
printf("Could not read Manifest.plist, switching to full backup mode.\n");
is_full_backup = 1;
@@ -932,10 +912,10 @@ int main(int argc, char *argv[])
}
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);
+ info_plist = mobilebackup_factory_info_plist_new(udid);
+ plist_write_to_file(info_plist, info_path, PLIST_FORMAT_XML, 0);
}
- g_free(info_path);
+ free(info_path);
plist_free(info_plist);
info_plist = NULL;
@@ -965,7 +945,7 @@ int main(int argc, char *argv[])
} else if (err == MOBILEBACKUP_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");
+ printf("ERROR: Could not start backup process: unspecified error occurred (%d)\n", err);
}
break;
}
@@ -985,8 +965,9 @@ int main(int argc, char *argv[])
char *filename_mddata = NULL;
char *filename_source = NULL;
char *format_size = NULL;
- gboolean is_manifest = FALSE;
+ int is_manifest = 0;
uint8_t b = 0;
+ uint64_t u64val = 0;
/* process series of DLSendFile messages */
do {
@@ -996,7 +977,7 @@ int main(int argc, char *argv[])
sleep(2);
goto files_out;
}
-
+
node = plist_array_get_item(message, 0);
/* get out if we don't get a DLSendFile */
@@ -1010,16 +991,16 @@ int main(int argc, char *argv[])
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);
+ format_size = string_format_size(backup_total_size);
printf("Backup data requires %s on the disk.\n", format_size);
- g_free(format_size);
+ 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;
+ plist_get_uint_val(node, &u64val);
+ file_status = u64val;
/* get source filename */
node = plist_dict_get_item(node_tmp, "BackupManifestKey");
@@ -1027,7 +1008,7 @@ int main(int argc, char *argv[])
if (node) {
plist_get_bool_val(node, &b);
}
- is_manifest = (b == 1) ? TRUE: FALSE;
+ is_manifest = (b == 1) ? 1 : 0;
if ((hunk_index == 0) && (!is_manifest)) {
/* get source filename */
@@ -1040,17 +1021,17 @@ int main(int argc, char *argv[])
plist_get_uint_val(node, &file_size);
backup_real_size += file_size;
- format_size = g_format_size_for_display(backup_real_size);
+ format_size = string_format_size(backup_real_size);
printf("(%s", format_size);
- g_free(format_size);
+ free(format_size);
- format_size = g_format_size_for_display(backup_total_size);
+ format_size = string_format_size(backup_total_size);
printf("/%s): ", format_size);
- g_free(format_size);
+ free(format_size);
- format_size = g_format_size_for_display(file_size);
+ format_size = string_format_size(file_size);
printf("Receiving file %s (%s)... \n", filename_source, format_size);
- g_free(format_size);
+ free(format_size);
if (filename_source)
free(filename_source);
@@ -1071,9 +1052,9 @@ int main(int argc, char *argv[])
remove(filename_mdinfo);
node = plist_dict_get_item(node_tmp, "BackupFileInfo");
- plist_write_to_filename(node, filename_mdinfo, PLIST_FORMAT_BINARY);
+ plist_write_to_file(node, filename_mdinfo, PLIST_FORMAT_BINARY, 0);
- g_free(filename_mdinfo);
+ free(filename_mdinfo);
}
file_index++;
@@ -1107,15 +1088,14 @@ int main(int argc, char *argv[])
free(buffer);
buffer = NULL;
- g_free(filename_mddata);
+ 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));
+ } else if (file_size > 0) {
+ print_progress((double)(file_size_current*100)/file_size);
}
}
@@ -1145,7 +1125,7 @@ files_out:
/* remove any atomic Manifest.plist.tmp */
if (manifest_path)
- g_free(manifest_path);
+ free(manifest_path);
manifest_path = mobilebackup_build_path(backup_directory, "Manifest", ".plist.tmp");
if (stat(manifest_path, &st) == 0)
@@ -1184,7 +1164,7 @@ files_out:
if (manifest_plist) {
remove(manifest_path);
printf("Storing Manifest.plist...\n");
- plist_write_to_filename(manifest_plist, manifest_path, PLIST_FORMAT_XML);
+ plist_write_to_file(manifest_plist, manifest_path, PLIST_FORMAT_XML, 0);
}
backup_ok = 1;
@@ -1215,21 +1195,21 @@ files_out:
}
/* now make sure backup integrity is ok! verify all files */
printf("Reading existing Manifest.\n");
- plist_read_from_filename(&manifest_plist, manifest_path);
+ plist_read_from_file(manifest_path, &manifest_plist, NULL);
if (!manifest_plist) {
printf("Could not read Manifest.plist. Aborting.\n");
break;
}
printf("Verifying backup integrity, please wait.\n");
- char *bin = NULL;
+ unsigned 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_get_data_val(node, (char**)&bin, &binsize);
plist_t backup_data = NULL;
if (bin) {
char *auth_ver = NULL;
@@ -1246,7 +1226,7 @@ files_out:
if (auth_sig && (auth_sig_len == 20)) {
/* calculate the sha1, then compare */
unsigned char data_sha1[20];
- sha1_of_data(bin, binsize, data_sha1);
+ sha1(bin, binsize, data_sha1);
if (compare_hash(auth_sig, data_sha1, 20)) {
printf("AuthSignature is valid\n");
} else {
@@ -1255,12 +1235,12 @@ files_out:
} else {
printf("Could not get AuthSignature from manifest!\n");
}
- g_free(auth_sig);
+ free(auth_sig);
} else if (auth_ver) {
- printf("Unknown AuthVersion '%s', cannot verify AuthSignature\n", auth_ver);
+ printf("Unknown AuthVersion '%s', cannot verify AuthSignature\n", auth_ver);
}
- plist_from_bin(bin, (uint32_t)binsize, &backup_data);
- g_free(bin);
+ plist_from_bin((char*)bin, (uint32_t)binsize, &backup_data);
+ free(bin);
}
if (!backup_data) {
printf("Could not read plist from Manifest.plist Data key!\n");
@@ -1312,7 +1292,7 @@ files_out:
} 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);
+ printf("ERROR: Could not start restore process: unspecified error occurred (%d)\n", err);
}
plist_free(backup_data);
break;
@@ -1342,7 +1322,7 @@ files_out:
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);
+ plist_read_from_file(file_info_path, &file_info, NULL);
/* get encryption state */
tmp_node = plist_dict_get_item(file_info, "IsEncrypted");
@@ -1360,42 +1340,62 @@ files_out:
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));
+ plist_dict_set_item(file_info, "DLFileAttributesKey", plist_copy(node));
+ plist_dict_set_item(file_info, "DLFileSource", plist_new_string(file_info_path));
+ plist_dict_set_item(file_info, "DLFileDest", plist_new_string("/tmp/RestoreFile.plist"));
+ plist_dict_set_item(file_info, "DLFileIsEncrypted", plist_new_bool(is_encrypted));
+ plist_dict_set_item(file_info, "DLFileOffsetKey", plist_new_uint(file_offset));
+ plist_dict_set_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);
+
+ /* determine file size */
+#ifdef WIN32
+ struct _stati64 fst;
+ if (_stati64(file_info_path, &fst) != 0)
+#else
+ struct stat fst;
+ if (stat(file_info_path, &fst) != 0)
+#endif
+ {
+ printf("ERROR: stat() failed for '%s': %s\n", file_info_path, strerror(errno));
+ free(file_info_path);
+ break;
+ }
+ length = fst.st_size;
+
+ FILE *f = fopen(file_info_path, "rb");
+ if (!f) {
+ printf("ERROR: could not open local file '%s': %s\n", file_info_path, strerror(errno));
+ free(file_info_path);
+ break;
+ }
free(file_info_path);
/* send DLSendFile messages */
file_offset = 0;
do {
- if ((length-file_offset) <= 8192)
+ char buf[8192];
+ size_t len = fread(buf, 1, sizeof(buf), f);
+
+ if ((length-file_offset) <= sizeof(buf))
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_set_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));
+ plist_dict_set_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_new_data(buf, len));
plist_array_append_item(send_file_node, plist_copy(file_info));
err = mobilebackup_send(mobilebackup, send_file_node);
@@ -1413,13 +1413,13 @@ files_out:
}
}
- file_offset += 8192;
+ file_offset += len;
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;
@@ -1466,8 +1466,8 @@ files_out:
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"));
+ plist_dict_set_item(dict, "AppInfo", plist_copy(tmp_node));
+ plist_dict_set_item(dict, "BackupMessageTypeKey", plist_new_string("BackupMessageRestoreApplicationSent"));
array = plist_new_array();
plist_array_append_item(array, plist_new_string("DLMessageProcessMessage"));
@@ -1540,9 +1540,9 @@ files_out:
do_post_notification(NP_SYNC_DID_FINISH);
}
if (manifest_path)
- g_free(manifest_path);
+ free(manifest_path);
} else {
- printf("ERROR: Could not start service %s.\n", MOBILEBACKUP_SERVICE_NAME);
+ printf("ERROR: Could not start service %s: %s\n", MOBILEBACKUP_SERVICE_NAME, lockdownd_strerror(ldret));
lockdownd_client_free(client);
client = NULL;
}
@@ -1561,7 +1561,9 @@ files_out:
if (mobilebackup)
mobilebackup_client_free(mobilebackup);
- idevice_free(phone);
+ idevice_free(device);
+
+ free(udid);
return 0;
}
diff --git a/tools/idevicebackup2.c b/tools/idevicebackup2.c
new file mode 100644
index 0000000..c73b269
--- /dev/null
+++ b/tools/idevicebackup2.c
@@ -0,0 +1,2684 @@
+/*
+ * idevicebackup2.c
+ * Command line interface to use the device's backup and restore service
+ *
+ * Copyright (c) 2010-2022 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 <config.h>
+#endif
+
+#define TOOL_NAME "idevicebackup2"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <libgen.h>
+#include <ctype.h>
+#include <time.h>
+#include <getopt.h>
+
+#include <libimobiledevice/libimobiledevice.h>
+#include <libimobiledevice/lockdown.h>
+#include <libimobiledevice/mobilebackup2.h>
+#include <libimobiledevice/notification_proxy.h>
+#include <libimobiledevice/afc.h>
+#include <libimobiledevice/installation_proxy.h>
+#include <libimobiledevice/sbservices.h>
+#include <libimobiledevice/diagnostics_relay.h>
+#include <libimobiledevice-glue/utils.h>
+#include <plist/plist.h>
+
+#include <endianness.h>
+
+#define LOCK_ATTEMPTS 50
+#define LOCK_WAIT 200000
+
+#ifdef WIN32
+#include <windows.h>
+#include <conio.h>
+#define sleep(x) Sleep(x*1000)
+#ifndef ELOOP
+#define ELOOP 114
+#endif
+#else
+#include <termios.h>
+#include <sys/statvfs.h>
+#endif
+#include <sys/stat.h>
+
+#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;
+ }
+ 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_file(file_path, &status_plist, NULL);
+ 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 (ldret == LOCKDOWN_E_SUCCESS) {
+ 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;
+ }
+ if (rlen == 0) {
+ // device needs more time, waiting...
+ continue;
+ }
+ 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;
+ }
+ 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, int is_error)
+{
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] CMD [CMDOPTIONS] DIRECTORY\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Create or restore backup in/from the specified directory.\n"
+ "\n"
+ "CMD:\n"
+ " backup create backup for the device\n"
+ " --full force full backup from device.\n"
+ " restore restore last backup to the device\n"
+ " --system restore system files, too.\n"
+ " --no-reboot do NOT reboot the device when done (default: yes).\n"
+ " --copy create a copy of backup folder before restoring.\n"
+ " --settings restore device settings from the backup.\n"
+ " --remove remove items which are not being restored\n"
+ " --skip-apps do not trigger re-installation of apps after restore\n"
+ " --password PWD supply the password for the encrypted source backup\n"
+ " info show details about last completed backup of device\n"
+ " list list files of last completed backup in CSV format\n"
+ " unback unpack a completed backup in DIRECTORY/_unback_/\n"
+ " encryption on|off [PWD] enable or disable backup encryption\n"
+ " changepw [OLD NEW] change backup password on target device\n"
+ " cloud on|off enable or disable cloud use (requires iCloud account)\n"
+ "\n"
+ "NOTE: Passwords will be requested in interactive mode (-i) if omitted, or can\n"
+ "be passed via environment variable BACKUP_PASSWORD/BACKUP_PASSWORD_NEW.\n"
+ "See man page for further details.\n"
+ "\n"
+ "OPTIONS:\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ " -s, --source UDID use backup data from device specified by UDID\n"
+ " -n, --network connect to network device\n"
+ " -i, --interactive request passwords interactively\n"
+ " -d, --debug enable communication debugging\n"
+ " -h, --help prints usage information\n"
+ " -v, --version prints version information\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "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 = 0;
+ 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;
+
+#define OPT_SYSTEM 1
+#define OPT_REBOOT 2
+#define OPT_NO_REBOOT 3
+#define OPT_COPY 4
+#define OPT_SETTINGS 5
+#define OPT_REMOVE 6
+#define OPT_SKIP_APPS 7
+#define OPT_PASSWORD 8
+#define OPT_FULL 9
+
+ int c = 0;
+ const struct option longopts[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+ { "source", required_argument, NULL, 's' },
+ { "interactive", no_argument, NULL, 'i' },
+ { "network", no_argument, NULL, 'n' },
+ { "version", no_argument, NULL, 'v' },
+ // command options:
+ { "system", no_argument, NULL, OPT_SYSTEM },
+ { "reboot", no_argument, NULL, OPT_REBOOT },
+ { "no-reboot", no_argument, NULL, OPT_NO_REBOOT },
+ { "copy", no_argument, NULL, OPT_COPY },
+ { "settings", no_argument, NULL, OPT_SETTINGS },
+ { "remove", no_argument, NULL, OPT_REMOVE },
+ { "skip-apps", no_argument, NULL, OPT_SKIP_APPS },
+ { "password", required_argument, NULL, OPT_PASSWORD },
+ { "full", no_argument, NULL, OPT_FULL },
+ { NULL, 0, NULL, 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 */
+ while ((c = getopt_long(argc, argv, "dhu:s:inv", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ idevice_set_debug_level(1);
+ break;
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID argument must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ udid = strdup(optarg);
+ break;
+ case 's':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: SOURCE argument must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ source_udid = strdup(optarg);
+ break;
+ case 'i':
+ interactive_mode = 1;
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
+ return 0;
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
+ return 0;
+ case OPT_SYSTEM:
+ cmd_flags |= CMD_FLAG_RESTORE_SYSTEM_FILES;
+ break;
+ case OPT_REBOOT:
+ cmd_flags &= ~CMD_FLAG_RESTORE_NO_REBOOT;
+ break;
+ case OPT_NO_REBOOT:
+ cmd_flags |= CMD_FLAG_RESTORE_NO_REBOOT;
+ break;
+ case OPT_COPY:
+ cmd_flags |= CMD_FLAG_RESTORE_COPY_BACKUP;
+ break;
+ case OPT_SETTINGS:
+ cmd_flags |= CMD_FLAG_RESTORE_SETTINGS;
+ break;
+ case OPT_REMOVE:
+ cmd_flags |= CMD_FLAG_RESTORE_REMOVE_ITEMS;
+ break;
+ case OPT_SKIP_APPS:
+ cmd_flags |= CMD_FLAG_RESTORE_SKIP_APPS;
+ break;
+ case OPT_PASSWORD:
+ free(backup_password);
+ backup_password = strdup(optarg);
+ break;
+ case OPT_FULL:
+ cmd_flags |= CMD_FLAG_FORCE_FULL_BACKUP;
+ break;
+ default:
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!argv[0]) {
+ fprintf(stderr, "ERROR: No command specified.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+
+ if (!strcmp(argv[0], "backup")) {
+ cmd = CMD_BACKUP;
+ }
+ else if (!strcmp(argv[0], "restore")) {
+ cmd = CMD_RESTORE;
+ }
+ else if (!strcmp(argv[0], "cloud")) {
+ cmd = CMD_CLOUD;
+ i = 1;
+ if (!argv[i]) {
+ fprintf(stderr, "ERROR: No argument given for cloud command; requires either 'on' or 'off'.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+ if (!strcmp(argv[i], "on")) {
+ cmd_flags |= CMD_FLAG_CLOUD_ENABLE;
+ } else if (!strcmp(argv[i], "off")) {
+ cmd_flags |= CMD_FLAG_CLOUD_DISABLE;
+ } else {
+ fprintf(stderr, "ERROR: Invalid argument '%s' for cloud command; must be either 'on' or 'off'.\n", argv[i]);
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+ }
+ else if (!strcmp(argv[0], "info")) {
+ cmd = CMD_INFO;
+ verbose = 0;
+ }
+ else if (!strcmp(argv[0], "list")) {
+ cmd = CMD_LIST;
+ verbose = 0;
+ }
+ else if (!strcmp(argv[0], "unback")) {
+ cmd = CMD_UNBACK;
+ }
+ else if (!strcmp(argv[0], "encryption")) {
+ cmd = CMD_CHANGEPW;
+ i = 1;
+ if (!argv[i]) {
+ fprintf(stderr, "ERROR: No argument given for encryption command; requires either 'on' or 'off'.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+ if (!strcmp(argv[i], "on")) {
+ cmd_flags |= CMD_FLAG_ENCRYPTION_ENABLE;
+ } else if (!strcmp(argv[i], "off")) {
+ cmd_flags |= CMD_FLAG_ENCRYPTION_DISABLE;
+ } else {
+ fprintf(stderr, "ERROR: Invalid argument '%s' for encryption command; must be either 'on' or 'off'.\n", argv[i]);
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+ // check if a password was given on the command line
+ free(newpw);
+ newpw = NULL;
+ 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]);
+ }
+ }
+ }
+ else if (!strcmp(argv[0], "changepw")) {
+ cmd = CMD_CHANGEPW;
+ cmd_flags |= CMD_FLAG_ENCRYPTION_CHANGEPW;
+ // check if passwords were given on command line
+ free(newpw);
+ newpw = NULL;
+ free(backup_password);
+ backup_password = NULL;
+ i = 1;
+ if (argv[i]) {
+ backup_password = strdup(argv[i]);
+ i++;
+ if (!argv[i]) {
+ fprintf(stderr, "ERROR: Old and new passwords have to be passed as arguments for the changepw command\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+ newpw = strdup(argv[i]);
+ }
+ }
+
+ i++;
+ if (argv[i]) {
+ backup_directory = argv[i];
+ }
+
+ /* verify options */
+ if (cmd == -1) {
+ fprintf(stderr, "ERROR: Unsupported command '%s'.\n", argv[0]);
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+
+ if (cmd == CMD_CHANGEPW || cmd == CMD_CLOUD) {
+ backup_directory = (char*)".this_folder_is_not_present_on_purpose";
+ } else {
+ if (backup_directory == NULL) {
+ fprintf(stderr, "ERROR: No target backup directory specified.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+
+ /* verify if passed backup directory exists */
+ if (stat(backup_directory, &st) != 0) {
+ fprintf(stderr, "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) {
+ if (!newpw) {
+ newpw = getenv("BACKUP_PASSWORD_NEW");
+ if (newpw) {
+ newpw = strdup(newpw);
+ }
+ }
+ if (!backup_password) {
+ backup_password = getenv("BACKUP_PASSWORD");
+ if (backup_password) {
+ backup_password = strdup(backup_password);
+ }
+ }
+ }
+ 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_file(manifest_path, &manifest_plist, NULL);
+ 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) {
+ backup_password = getenv("BACKUP_PASSWORD");
+ if (backup_password) {
+ backup_password = strdup(backup_password);
+ }
+ }
+ 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_file(info_path, &info_plist, NULL);
+
+ 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;
+ }
+ if (aerr == AFC_E_OP_WOULD_BLOCK) {
+ usleep(LOCK_WAIT);
+ continue;
+ }
+
+ 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_file(info_plist, info_path, PLIST_FORMAT_XML, 0);
+ 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"));
+ plist_dict_set_item(opts, "RemoveItemsNotRestored", plist_new_bool(cmd_flags & CMD_FLAG_RESTORE_REMOVE_ITEMS));
+ 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;
+ }
+ 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 = getenv("BACKUP_PASSWORD");
+ if (newpw) {
+ newpw = strdup(newpw);
+ }
+ }
+ 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 = getenv("BACKUP_PASSWORD");
+ if (backup_password) {
+ backup_password = strdup(backup_password);
+ }
+ }
+ 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;
+}
+
diff --git a/tools/idevicebtlogger.c b/tools/idevicebtlogger.c
new file mode 100644
index 0000000..8de6b22
--- /dev/null
+++ b/tools/idevicebtlogger.c
@@ -0,0 +1,458 @@
+/*
+ * idevicebt_packet_logger.c
+ * Capture Bluetooth HCI traffic to native PKLG or PCAP
+ *
+ * Copyright (c) 2021 Geoffrey Kruse, All Rights Reserved.
+ * Copyright (c) 2022 Matthias Ringwald, All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define TOOL_NAME "idevicebtlogger"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <assert.h>
+#include <fcntl.h>
+
+#ifdef WIN32
+#include <windows.h>
+#define sleep(x) Sleep(x*1000)
+#else
+#include <arpa/inet.h>
+#endif
+
+
+#include <libimobiledevice/libimobiledevice.h>
+#include <libimobiledevice/bt_packet_logger.h>
+
+typedef enum {
+ HCI_COMMAND = 0x00,
+ HCI_EVENT = 0x01,
+ SENT_ACL_DATA = 0x02,
+ RECV_ACL_DATA = 0x03,
+ SENT_SCO_DATA = 0x08,
+ RECV_SCO_DATA = 0x09,
+} PacketLoggerPacketType;
+
+static int quit_flag = 0;
+static int exit_on_disconnect = 0;
+
+static char* udid = NULL;
+static idevice_t device = NULL;
+static bt_packet_logger_client_t bt_packet_logger = NULL;
+static int use_network = 0;
+static char* out_filename = NULL;
+static char* log_format_string = NULL;
+static FILE * packetlogger_file = NULL;
+
+static enum {
+ LOG_FORMAT_PACKETLOGGER,
+ LOG_FORMAT_PCAP
+} log_format = LOG_FORMAT_PACKETLOGGER;
+
+const uint8_t pcap_file_header[] = {
+ // Magic Number
+ 0xA1, 0xB2, 0xC3, 0xD4,
+ // Major / Minor Version
+ 0x00, 0x02, 0x00, 0x04,
+ // Reserved1
+ 0x00, 0x00, 0x00, 0x00,
+ // Reserved2
+ 0x00, 0x00, 0x00, 0x00,
+ // Snaplen == max packet size - use 2kB (larger than any ACL)
+ 0x00, 0x00, 0x08, 0x00,
+ // LinkType: DLT_BLUETOOTH_HCI_H4_WITH_PHDR
+ 0x00, 0x00, 0x00, 201,
+};
+
+static uint32_t big_endian_read_32(const uint8_t * buffer, int position)
+{
+ return ((uint32_t) buffer[position+3]) | (((uint32_t)buffer[position+2]) << 8) | (((uint32_t)buffer[position+1]) << 16) | (((uint32_t) buffer[position]) << 24);
+}
+
+static void big_endian_store_32(uint8_t * buffer, uint16_t position, uint32_t value)
+{
+ uint16_t pos = position;
+ buffer[pos++] = (uint8_t)(value >> 24);
+ buffer[pos++] = (uint8_t)(value >> 16);
+ buffer[pos++] = (uint8_t)(value >> 8);
+ buffer[pos++] = (uint8_t)(value);
+}
+
+/**
+ * Callback from the packet logger service to handle packets and log to PacketLogger format
+ */
+static void bt_packet_logger_callback_packetlogger(uint8_t * data, uint16_t len, void *user_data)
+{
+ (void) fwrite(data, 1, len, packetlogger_file);
+}
+
+/**
+ * Callback from the packet logger service to handle packets and log to pcap
+ */
+static void bt_packet_logger_callback_pcap(uint8_t * data, uint16_t len, void *user_data)
+{
+ // check len
+ if (len < 13) {
+ return;
+ }
+
+ // parse packet header (ignore len field)
+ uint32_t ts_secs = big_endian_read_32(data, 4);
+ uint32_t ts_us = big_endian_read_32(data, 8);
+ uint8_t packet_type = data[12];
+ data += 13;
+ len -= 13;
+
+ // map PacketLogger packet type onto PCAP direction flag and hci_h4_type
+ uint8_t direction_in = 0;
+ uint8_t hci_h4_type = 0xff;
+ switch(packet_type) {
+ case HCI_COMMAND:
+ hci_h4_type = 0x01;
+ direction_in = 0;
+ break;
+ case SENT_ACL_DATA:
+ hci_h4_type = 0x02;
+ direction_in = 0;
+ break;
+ case RECV_ACL_DATA:
+ hci_h4_type = 0x02;
+ direction_in = 1;
+ break;
+ case SENT_SCO_DATA:
+ hci_h4_type = 0x03;
+ direction_in = 0;
+ break;
+ case RECV_SCO_DATA:
+ hci_h4_type = 0x03;
+ direction_in = 1;
+ break;
+ case HCI_EVENT:
+ hci_h4_type = 0x04;
+ direction_in = 1;
+ break;
+ default:
+ // unknown packet logger type, drop packet
+ return;
+ }
+
+ // setup pcap record header, 4 byte direction flag, 1 byte HCI H4 packet type, data
+ uint8_t pcap_record_header[21];
+ big_endian_store_32(pcap_record_header, 0, ts_secs); // Timestamp seconds
+ big_endian_store_32(pcap_record_header, 4, ts_us); // Timestamp microseconds
+ big_endian_store_32(pcap_record_header, 8, 4 + 1 + len); // Captured Packet Length
+ big_endian_store_32(pcap_record_header, 12, 4 + 1 + len); // Original Packet Length
+ big_endian_store_32(pcap_record_header, 16, direction_in); // Direction: Incoming = 1
+ pcap_record_header[20] = hci_h4_type;
+
+ // write header
+ (void) fwrite(pcap_record_header, 1, sizeof(pcap_record_header), packetlogger_file);
+
+ // write packet
+ (void) fwrite(data, 1, len, packetlogger_file);
+
+ // flush
+ (void) fflush(packetlogger_file);
+}
+
+/**
+ * Disable HCI log capture
+ */
+static void stop_logging(void)
+{
+ fflush(NULL);
+
+ if (bt_packet_logger) {
+ bt_packet_logger_client_free(bt_packet_logger);
+ bt_packet_logger = NULL;
+ }
+
+ if (device) {
+ idevice_free(device);
+ device = NULL;
+ }
+}
+
+/**
+ * Enable HCI log capture
+ */
+static int start_logging(void)
+{
+ idevice_error_t ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX);
+ if (ret != IDEVICE_E_SUCCESS) {
+ fprintf(stderr, "Device with udid %s not found!?\n", udid);
+ return -1;
+ }
+
+ lockdownd_client_t lockdown = NULL;
+ lockdownd_error_t lerr = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME);
+ if (lerr != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "ERROR: Could not connect to lockdownd: %d\n", lerr);
+ idevice_free(device);
+ device = NULL;
+ return -1;
+ }
+
+ /* start bt_packet_logger service */
+ bt_packet_logger_client_start_service(device, &bt_packet_logger, TOOL_NAME);
+
+ /* start capturing bt_packet_logger */
+ void (*callback)(uint8_t * data, uint16_t len, void *user_data);
+ switch (log_format){
+ case LOG_FORMAT_PCAP:
+ callback = bt_packet_logger_callback_pcap;
+ break;
+ case LOG_FORMAT_PACKETLOGGER:
+ callback = bt_packet_logger_callback_packetlogger;
+ break;
+ default:
+ assert(0);
+ return 0;
+ }
+ bt_packet_logger_error_t serr = bt_packet_logger_start_capture(bt_packet_logger, callback, NULL);
+ if (serr != BT_PACKET_LOGGER_E_SUCCESS) {
+ fprintf(stderr, "ERROR: Unable to start capturing bt_packet_logger.\n");
+ bt_packet_logger_client_free(bt_packet_logger);
+ bt_packet_logger = NULL;
+ idevice_free(device);
+ device = NULL;
+ return -1;
+ }
+
+ fprintf(stderr, "[connected:%s]\n", udid);
+ fflush(stderr);
+
+ return 0;
+}
+
+/**
+ * Callback for device events
+ */
+static void device_event_cb(const idevice_event_t* event, void* userdata)
+{
+ if (use_network && event->conn_type != CONNECTION_NETWORK) {
+ return;
+ } else if (!use_network && event->conn_type != CONNECTION_USBMUXD) {
+ return;
+ }
+ if (event->event == IDEVICE_DEVICE_ADD) {
+ if (!bt_packet_logger) {
+ if (!udid) {
+ udid = strdup(event->udid);
+ }
+ if (strcmp(udid, event->udid) == 0) {
+ if (start_logging() != 0) {
+ fprintf(stderr, "Could not start logger for udid %s\n", udid);
+ }
+ }
+ }
+ } else if (event->event == IDEVICE_DEVICE_REMOVE) {
+ if (bt_packet_logger && (strcmp(udid, event->udid) == 0)) {
+ stop_logging();
+ fprintf(stderr, "[disconnected:%s]\n", udid);
+ if (exit_on_disconnect) {
+ quit_flag++;
+ }
+ }
+ }
+}
+
+/**
+ * signal handler function for cleaning up properly
+ */
+static void clean_exit(int sig)
+{
+ fprintf(stderr, "\nExiting...\n");
+ quit_flag++;
+}
+
+/**
+ * print usage information
+ */
+static void print_usage(int argc, char **argv, int is_error)
+{
+ char *name = NULL;
+ name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] <FILE>\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n" \
+ "Capture HCI packets from a connected device.\n" \
+ "\n" \
+ "OPTIONS:\n" \
+ " -u, --udid UDID target specific device by UDID\n" \
+ " -n, --network connect to network device\n" \
+ " -f, --format FORMAT logging format: packetlogger (default) or pcap\n" \
+ " -x, --exit exit when device disconnects\n" \
+ " -h, --help prints usage information\n" \
+ " -d, --debug enable communication debugging\n" \
+ " -v, --version prints version information\n" \
+ "\n" \
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
+}
+
+/**
+ * Program entry
+ */
+int main(int argc, char *argv[])
+{
+ int c = 0;
+ const struct option longopts[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+ { "format", required_argument, NULL, 'f' },
+ { "network", no_argument, NULL, 'n' },
+ { "exit", no_argument, NULL, 'x' },
+ { "version", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0}
+ };
+
+ signal(SIGINT, clean_exit);
+ signal(SIGTERM, clean_exit);
+#ifndef WIN32
+ signal(SIGQUIT, clean_exit);
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ while ((c = getopt_long(argc, argv, "dhu:f:nxv", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ idevice_set_debug_level(1);
+ break;
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ free(udid);
+ udid = strdup(optarg);
+ break;
+ case 'f':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: FORMAT must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ free(log_format_string);
+ log_format_string = strdup(optarg);
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 'x':
+ exit_on_disconnect = 1;
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
+ return 0;
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
+ return 0;
+ default:
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ }
+
+ if (optind < argc) {
+ out_filename = argv[optind];
+ // printf("Output File: %s\n", out_filename);
+ }
+ else {
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+
+ if (log_format_string != NULL){
+ if (strcmp("packetlogger", log_format_string) == 0){
+ log_format = LOG_FORMAT_PACKETLOGGER;
+ } else if (strcmp("pcap", log_format_string) == 0){
+ log_format = LOG_FORMAT_PCAP;
+ } else {
+ printf("Unknown logging format: '%s'\n", log_format_string);
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ }
+
+ int num = 0;
+ idevice_info_t *devices = NULL;
+ idevice_get_device_list_extended(&devices, &num);
+ idevice_device_list_extended_free(devices);
+ if (num == 0) {
+ if (!udid) {
+ fprintf(stderr, "No device found. Plug in a device or pass UDID with -u to wait for device to be available.\n");
+ return -1;
+ } else {
+ fprintf(stderr, "Waiting for device with UDID %s to become available...\n", udid);
+ }
+ }
+
+ // support streaming to stdout
+ if (strcmp(out_filename, "-") == 0){
+ packetlogger_file = stdout;
+ } else {
+ packetlogger_file = fopen(out_filename, "wb");
+ }
+
+
+ if (packetlogger_file == NULL){
+ fprintf(stderr, "Failed to open file %s, errno = %d\n", out_filename, errno);
+ return -2;
+ }
+
+ switch (log_format){
+ case LOG_FORMAT_PCAP:
+ // printf("Output Format: PCAP\n");
+ // write PCAP file header
+ (void) fwrite(&pcap_file_header, 1, sizeof(pcap_file_header), packetlogger_file);
+ break;
+ case LOG_FORMAT_PACKETLOGGER:
+ printf("Output Format: PacketLogger\n");
+ break;
+ default:
+ assert(0);
+ return -2;
+ }
+ idevice_subscription_context_t context = NULL;
+ idevice_events_subscribe(&context, device_event_cb, NULL);
+
+ while (!quit_flag) {
+ sleep(1);
+ }
+
+ idevice_events_unsubscribe(context);
+ stop_logging();
+
+ fclose(packetlogger_file);
+
+ free(udid);
+
+ return 0;
+}
diff --git a/tools/idevicecrashreport.c b/tools/idevicecrashreport.c
new file mode 100644
index 0000000..09bd537
--- /dev/null
+++ b/tools/idevicecrashreport.c
@@ -0,0 +1,529 @@
+/*
+ * idevicecrashreport.c
+ * Simple utility to move crash reports from a device to a local directory.
+ *
+ * Copyright (c) 2014 Martin Szulecki. All Rights Reserved.
+ * Copyright (c) 2014 Nikias Bassen. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "idevicecrashreport"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+#ifndef WIN32
+#include <signal.h>
+#endif
+#include <libimobiledevice-glue/utils.h>
+
+#include <libimobiledevice/libimobiledevice.h>
+#include <libimobiledevice/lockdown.h>
+#include <libimobiledevice/service.h>
+#include <libimobiledevice/afc.h>
+#include <plist/plist.h>
+
+#ifdef WIN32
+#include <windows.h>
+#define S_IFLNK S_IFREG
+#define S_IFSOCK S_IFREG
+#endif
+
+#define CRASH_REPORT_MOVER_SERVICE "com.apple.crashreportmover"
+#define CRASH_REPORT_COPY_MOBILE_SERVICE "com.apple.crashreportcopymobile"
+
+const char* target_directory = NULL;
+static int extract_raw_crash_reports = 0;
+static int keep_crash_reports = 0;
+
+static int file_exists(const char* path)
+{
+ struct stat tst;
+#ifdef WIN32
+ return (stat(path, &tst) == 0);
+#else
+ return (lstat(path, &tst) == 0);
+#endif
+}
+
+static int extract_raw_crash_report(const char* filename)
+{
+ int res = 0;
+ plist_t report = NULL;
+ char* raw = NULL;
+ char* raw_filename = strdup(filename);
+
+ /* create filename with '.crash' extension */
+ char* p = strrchr(raw_filename, '.');
+ if ((p == NULL) || (strcmp(p, ".plist") != 0)) {
+ free(raw_filename);
+ return res;
+ }
+ strcpy(p, ".crash");
+
+ /* read plist crash report */
+ if (plist_read_from_file(filename, &report, NULL)) {
+ plist_t description_node = plist_dict_get_item(report, "description");
+ if (description_node && plist_get_node_type(description_node) == PLIST_STRING) {
+ plist_get_string_val(description_node, &raw);
+
+ if (raw != NULL) {
+ /* write file */
+ buffer_write_to_filename(raw_filename, raw, strlen(raw));
+ free(raw);
+ res = 1;
+ }
+ }
+ }
+
+ if (report)
+ plist_free(report);
+
+ if (raw_filename)
+ free(raw_filename);
+
+ return res;
+}
+
+static int afc_client_copy_and_remove_crash_reports(afc_client_t afc, const char* device_directory, const char* host_directory, const char* filename_filter)
+{
+ afc_error_t afc_error;
+ int k;
+ int res = -1;
+ int crash_report_count = 0;
+ uint64_t handle;
+ char source_filename[512];
+ char target_filename[512];
+
+ if (!afc)
+ return res;
+
+ char** list = NULL;
+ afc_error = afc_read_directory(afc, device_directory, &list);
+ if (afc_error != AFC_E_SUCCESS) {
+ fprintf(stderr, "ERROR: Could not read device directory '%s'\n", device_directory);
+ return res;
+ }
+
+ /* ensure we have a trailing slash */
+ strcpy(source_filename, device_directory);
+ if (source_filename[strlen(source_filename)-1] != '/') {
+ strcat(source_filename, "/");
+ }
+ int device_directory_length = strlen(source_filename);
+
+ /* ensure we have a trailing slash */
+ strcpy(target_filename, host_directory);
+ if (target_filename[strlen(target_filename)-1] != '/') {
+ strcat(target_filename, "/");
+ }
+ int host_directory_length = strlen(target_filename);
+
+ /* loop over file entries */
+ for (k = 0; list[k]; k++) {
+ if (!strcmp(list[k], ".") || !strcmp(list[k], "..")) {
+ continue;
+ }
+
+ char **fileinfo = NULL;
+ struct stat stbuf;
+ memset(&stbuf, '\0', sizeof(struct stat));
+
+ /* assemble absolute source filename */
+ strcpy(((char*)source_filename) + device_directory_length, list[k]);
+
+ /* assemble absolute target filename */
+#ifdef WIN32
+ /* replace every ':' with '-' since ':' is an illegal character for file names in windows */
+ char* current_pos = strchr(list[k], ':');
+ while (current_pos) {
+ *current_pos = '-';
+ current_pos = strchr(current_pos, ':');
+ }
+#endif
+ char* p = strrchr(list[k], '.');
+ if (p != NULL && !strncmp(p, ".synced", 7)) {
+ /* make sure to strip ".synced" extension as seen on iOS 5 */
+ size_t newlen = p - list[k];
+ strncpy(((char*)target_filename) + host_directory_length, list[k], newlen);
+ target_filename[host_directory_length + newlen] = '\0';
+ } else {
+ strcpy(((char*)target_filename) + host_directory_length, list[k]);
+ }
+
+ /* get file information */
+ afc_get_file_info(afc, source_filename, &fileinfo);
+ if (!fileinfo) {
+ printf("Failed to read information for '%s'. Skipping...\n", source_filename);
+ continue;
+ }
+
+ /* parse file information */
+ int i;
+ for (i = 0; fileinfo[i]; i+=2) {
+ if (!strcmp(fileinfo[i], "st_size")) {
+ stbuf.st_size = atoll(fileinfo[i+1]);
+ } else if (!strcmp(fileinfo[i], "st_ifmt")) {
+ if (!strcmp(fileinfo[i+1], "S_IFREG")) {
+ stbuf.st_mode = S_IFREG;
+ } else if (!strcmp(fileinfo[i+1], "S_IFDIR")) {
+ stbuf.st_mode = S_IFDIR;
+ } else if (!strcmp(fileinfo[i+1], "S_IFLNK")) {
+ stbuf.st_mode = S_IFLNK;
+ } else if (!strcmp(fileinfo[i+1], "S_IFBLK")) {
+ stbuf.st_mode = S_IFBLK;
+ } else if (!strcmp(fileinfo[i+1], "S_IFCHR")) {
+ stbuf.st_mode = S_IFCHR;
+ } else if (!strcmp(fileinfo[i+1], "S_IFIFO")) {
+ stbuf.st_mode = S_IFIFO;
+ } else if (!strcmp(fileinfo[i+1], "S_IFSOCK")) {
+ stbuf.st_mode = S_IFSOCK;
+ }
+ } else if (!strcmp(fileinfo[i], "st_nlink")) {
+ stbuf.st_nlink = atoi(fileinfo[i+1]);
+ } else if (!strcmp(fileinfo[i], "st_mtime")) {
+ stbuf.st_mtime = (time_t)(atoll(fileinfo[i+1]) / 1000000000);
+ } else if (!strcmp(fileinfo[i], "LinkTarget")) {
+ /* report latest crash report filename */
+ printf("Link: %s\n", (char*)target_filename + strlen(target_directory));
+
+ /* remove any previous symlink */
+ if (file_exists(target_filename)) {
+ remove(target_filename);
+ }
+
+#ifndef WIN32
+ /* use relative filename */
+ char* b = strrchr(fileinfo[i+1], '/');
+ if (b == NULL) {
+ b = fileinfo[i+1];
+ } else {
+ b++;
+ }
+
+ /* create a symlink pointing to latest log */
+ if (symlink(b, target_filename) < 0) {
+ fprintf(stderr, "Can't create symlink to %s\n", b);
+ }
+#endif
+
+ if (!keep_crash_reports)
+ afc_remove_path(afc, source_filename);
+
+ res = 0;
+ }
+ }
+
+ /* free file information */
+ afc_dictionary_free(fileinfo);
+
+ /* recurse into child directories */
+ if (S_ISDIR(stbuf.st_mode)) {
+#ifdef WIN32
+ mkdir(target_filename);
+#else
+ mkdir(target_filename, 0755);
+#endif
+ res = afc_client_copy_and_remove_crash_reports(afc, source_filename, target_filename, filename_filter);
+
+ /* remove directory from device */
+ if (!keep_crash_reports)
+ afc_remove_path(afc, source_filename);
+ } else if (S_ISREG(stbuf.st_mode)) {
+ if (filename_filter != NULL && strstr(source_filename, filename_filter) == NULL) {
+ continue;
+ }
+
+ /* copy file to host */
+ afc_error = afc_file_open(afc, source_filename, AFC_FOPEN_RDONLY, &handle);
+ if(afc_error != AFC_E_SUCCESS) {
+ if (afc_error == AFC_E_OBJECT_NOT_FOUND) {
+ continue;
+ }
+ fprintf(stderr, "Unable to open device file '%s' (%d). Skipping...\n", source_filename, afc_error);
+ continue;
+ }
+
+ FILE* output = fopen(target_filename, "wb");
+ if(output == NULL) {
+ fprintf(stderr, "Unable to open local file '%s'. Skipping...\n", target_filename);
+ afc_file_close(afc, handle);
+ continue;
+ }
+
+ printf("%s: %s\n", (keep_crash_reports ? "Copy": "Move") , (char*)target_filename + strlen(target_directory));
+
+ uint32_t bytes_read = 0;
+ uint32_t bytes_total = 0;
+ unsigned char data[0x1000];
+
+ afc_error = afc_file_read(afc, handle, (char*)data, 0x1000, &bytes_read);
+ while(afc_error == AFC_E_SUCCESS && bytes_read > 0) {
+ fwrite(data, 1, bytes_read, output);
+ bytes_total += bytes_read;
+ afc_error = afc_file_read(afc, handle, (char*)data, 0x1000, &bytes_read);
+ }
+ afc_file_close(afc, handle);
+ fclose(output);
+
+ if ((uint32_t)stbuf.st_size != bytes_total) {
+ fprintf(stderr, "File size mismatch. Skipping...\n");
+ continue;
+ }
+
+ /* remove file from device */
+ if (!keep_crash_reports) {
+ afc_remove_path(afc, source_filename);
+ }
+
+ /* extract raw crash information into separate '.crash' file */
+ if (extract_raw_crash_reports) {
+ extract_raw_crash_report(target_filename);
+ }
+
+ crash_report_count++;
+
+ res = 0;
+ }
+ }
+ afc_dictionary_free(list);
+
+ /* no reports, no error */
+ if (crash_report_count == 0)
+ res = 0;
+
+ return res;
+}
+
+static void print_usage(int argc, char **argv, int is_error)
+{
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] DIRECTORY\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Move crash reports from device to a local DIRECTORY.\n"
+ "\n"
+ "OPTIONS:\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ " -n, --network connect to network device\n"
+ " -e, --extract extract raw crash report into separate '.crash' file\n"
+ " -k, --keep copy but do not remove crash reports from device\n"
+ " -d, --debug enable communication debugging\n"
+ " -f, --filter NAME filter crash reports by NAME (case sensitive)\n"
+ " -h, --help prints usage information\n"
+ " -v, --version prints version information\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
+}
+
+int main(int argc, char* argv[])
+{
+ idevice_t device = NULL;
+ lockdownd_client_t lockdownd = NULL;
+ afc_client_t afc = NULL;
+
+ idevice_error_t device_error = IDEVICE_E_SUCCESS;
+ lockdownd_error_t lockdownd_error = LOCKDOWN_E_SUCCESS;
+ afc_error_t afc_error = AFC_E_SUCCESS;
+
+ const char* udid = NULL;
+ int use_network = 0;
+ const char* filename_filter = NULL;
+
+ int c = 0;
+ const struct option longopts[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+ { "network", no_argument, NULL, 'n' },
+ { "version", no_argument, NULL, 'v' },
+ { "filter", required_argument, NULL, 'f' },
+ { "extract", no_argument, NULL, 'e' },
+ { "keep", no_argument, NULL, 'k' },
+ { NULL, 0, NULL, 0}
+ };
+
+#ifndef WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ /* parse cmdline args */
+ while ((c = getopt_long(argc, argv, "dhu:nvf:ek", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ idevice_set_debug_level(1);
+ break;
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID argument must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ udid = optarg;
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
+ return 0;
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
+ return 0;
+ case 'f':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: filter argument must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ filename_filter = optarg;
+ break;
+ case 'e':
+ extract_raw_crash_reports = 1;
+ break;
+ case 'k':
+ keep_crash_reports = 1;
+ break;
+ default:
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* ensure a target directory was supplied */
+ if (!argv[0]) {
+ fprintf(stderr, "ERROR: missing target directory.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+ target_directory = argv[0];
+
+ /* check if target directory exists */
+ if (!file_exists(target_directory)) {
+ fprintf(stderr, "ERROR: Directory '%s' does not exist.\n", target_directory);
+ return 1;
+ }
+
+ device_error = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX);
+ if (device_error != IDEVICE_E_SUCCESS) {
+ if (udid) {
+ printf("No device found with udid %s.\n", udid);
+ } else {
+ printf("No device found.\n");
+ }
+ return -1;
+ }
+
+ lockdownd_error = lockdownd_client_new_with_handshake(device, &lockdownd, TOOL_NAME);
+ if (lockdownd_error != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", lockdownd_error);
+ idevice_free(device);
+ return -1;
+ }
+
+ /* start crash log mover service */
+ lockdownd_service_descriptor_t service = NULL;
+ lockdownd_error = lockdownd_start_service(lockdownd, CRASH_REPORT_MOVER_SERVICE, &service);
+ if (lockdownd_error != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "ERROR: Could not start service %s: %s\n", CRASH_REPORT_MOVER_SERVICE, lockdownd_strerror(lockdownd_error));
+ lockdownd_client_free(lockdownd);
+ idevice_free(device);
+ return -1;
+ }
+
+ /* trigger move operation on device */
+ service_client_t svcmove = NULL;
+ service_error_t service_error = service_client_new(device, service, &svcmove);
+ lockdownd_service_descriptor_free(service);
+ service = NULL;
+ if (service_error != SERVICE_E_SUCCESS) {
+ lockdownd_client_free(lockdownd);
+ idevice_free(device);
+ return -1;
+ }
+
+ /* read "ping" message which indicates the crash logs have been moved to a safe harbor */
+ char *ping = malloc(4);
+ memset(ping, '\0', 4);
+ int attempts = 0;
+ while ((strncmp(ping, "ping", 4) != 0) && (attempts < 10)) {
+ uint32_t bytes = 0;
+ service_error = service_receive_with_timeout(svcmove, ping, 4, &bytes, 2000);
+ if (service_error == SERVICE_E_SUCCESS || service_error == SERVICE_E_TIMEOUT) {
+ attempts++;
+ continue;
+ }
+
+ fprintf(stderr, "ERROR: Crash logs could not be moved. Connection interrupted (%d).\n", service_error);
+ break;
+ }
+ service_client_free(svcmove);
+ free(ping);
+
+ if (device_error != IDEVICE_E_SUCCESS || attempts > 10) {
+ fprintf(stderr, "ERROR: Failed to receive ping message from crash report mover.\n");
+ lockdownd_client_free(lockdownd);
+ idevice_free(device);
+ return -1;
+ }
+
+ lockdownd_error = lockdownd_start_service(lockdownd, CRASH_REPORT_COPY_MOBILE_SERVICE, &service);
+ if (lockdownd_error != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "ERROR: Could not start service %s: %s\n", CRASH_REPORT_COPY_MOBILE_SERVICE, lockdownd_strerror(lockdownd_error));
+ lockdownd_client_free(lockdownd);
+ idevice_free(device);
+ return -1;
+ }
+ lockdownd_client_free(lockdownd);
+
+ afc = NULL;
+ afc_error = afc_client_new(device, service, &afc);
+ if(afc_error != AFC_E_SUCCESS) {
+ lockdownd_client_free(lockdownd);
+ idevice_free(device);
+ return -1;
+ }
+
+ if (service) {
+ lockdownd_service_descriptor_free(service);
+ service = NULL;
+ }
+
+ /* recursively copy crash reports from the device to a local directory */
+ if (afc_client_copy_and_remove_crash_reports(afc, ".", target_directory, filename_filter) < 0) {
+ fprintf(stderr, "ERROR: Failed to get crash reports from device.\n");
+ afc_client_free(afc);
+ idevice_free(device);
+ return -1;
+ }
+
+ printf("Done.\n");
+
+ afc_client_free(afc);
+ idevice_free(device);
+
+ return 0;
+}
diff --git a/tools/idevicedate.c b/tools/idevicedate.c
index e4e0a33..d05f63e 100644
--- a/tools/idevicedate.c
+++ b/tools/idevicedate.c
@@ -1,6 +1,6 @@
/*
* idevicedate.c
- * Simple utility to get and set the clock on an iDevice
+ * Simple utility to get and set the clock on a device
*
* Copyright (c) 2011 Martin Szulecki All Rights Reserved.
*
@@ -8,176 +8,249 @@
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "idevicedate"
+
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <getopt.h>
#include <time.h>
#if HAVE_LANGINFO_CODESET
-# include <langinfo.h>
+#include <langinfo.h>
+#endif
+#ifndef WIN32
+#include <signal.h>
#endif
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/lockdown.h>
#ifdef _DATE_FMT
-# define DATE_FMT_LANGINFO() nl_langinfo (_DATE_FMT)
+#define DATE_FMT_LANGINFO nl_langinfo (_DATE_FMT)
+#else
+#ifdef WIN32
+#define DATE_FMT_LANGINFO "%a %b %#d %H:%M:%S %Z %Y"
#else
-# define DATE_FMT_LANGINFO() ""
+#define DATE_FMT_LANGINFO "%a %b %e %H:%M:%S %Z %Y"
+#endif
#endif
-static void print_usage(int argc, char **argv)
+static void print_usage(int argc, char **argv, int is_error)
{
- char *name = NULL;
-
- name = strrchr(argv[0], '/');
- printf("Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0]));
- printf("Display the current date or set it on an iDevice.\n\n");
- printf(" -d, --debug\t\tenable communication debugging\n");
- printf(" -u, --uuid UUID\ttarget specific device by its 40-digit device UUID\n");
- printf(" -s, --set TIMESTAMP\tset UTC time described by TIMESTAMP\n");
- printf(" -c, --sync\t\tset time of device to current system time\n");
- printf(" -h, --help\t\tprints usage information\n");
- printf("\n");
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Display the current date or set it on a device.\n"
+ "\n"
+ "NOTE: Setting the time on iOS 6 and later is only supported\n"
+ " in the setup wizard screens before device activation.\n"
+ "\n"
+ "OPTIONS:\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ " -n, --network connect to network device\n"
+ " -s, --set TIMESTAMP set UTC time described by TIMESTAMP\n"
+ " -c, --sync set time of device to current system time\n"
+ " -d, --debug enable communication debugging\n"
+ " -h, --help prints usage information\n"
+ " -v, --version prints version information\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
}
int main(int argc, char *argv[])
{
lockdownd_client_t client = NULL;
- idevice_t phone = NULL;
+ lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR;
+ idevice_t device = NULL;
idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR;
- int i;
- char uuid[41];
+ const char* udid = NULL;
+ int use_network = 0;
time_t setdate = 0;
plist_t node = NULL;
- uuid[0] = 0;
+ int node_type = -1;
uint64_t datetime = 0;
time_t rawtime;
struct tm * tmp;
- char const *format = NULL;
char buffer[80];
+ int result = 0;
+
+ int c = 0;
+ const struct option longopts[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+ { "network", no_argument, NULL, 'n' },
+ { "version", no_argument, NULL, 'v' },
+ { "set", required_argument, NULL, 's' },
+ { "sync", no_argument, NULL, 'c' },
+ { NULL, 0, NULL, 0}
+ };
+#ifndef WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
/* parse cmdline args */
- for (i = 1; i < argc; i++) {
- if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) {
+ while ((c = getopt_long(argc, argv, "dhu:nvs:c", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
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;
+ break;
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID argument must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
}
- strcpy(uuid, argv[i]);
- continue;
- }
- else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--set")) {
- i++;
- if (!argv[i] || (strlen(argv[i]) <= 1)) {
- print_usage(argc, argv);
- return 0;
+ udid = optarg;
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
+ return 0;
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
+ return 0;
+ case 's':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: set argument must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
}
- setdate = atoi(argv[i]);
+ setdate = atoi(optarg);
if (setdate == 0) {
- printf("ERROR: Invalid timestamp value.\n");
- print_usage(argc, argv);
+ fprintf(stderr, "ERROR: Invalid timestamp value.\n");
+ print_usage(argc, argv, 1);
return 0;
}
- continue;
- }
- else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--sync")) {
- i++;
+ break;
+ case 'c':
/* get current time */
setdate = time(NULL);
/* convert it to local time which sets timezone/daylight variables */
tmp = localtime(&setdate);
/* recalculate to make it UTC */
setdate = mktime(tmp);
- continue;
- }
- else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
- print_usage(argc, argv);
- return 0;
- }
- else {
- print_usage(argc, argv);
- return 0;
+ break;
+ default:
+ print_usage(argc, argv, 1);
+ return 2;
}
}
+ argc -= optind;
+ argv += optind;
- /* determine a date format */
- if (!format) {
- format = DATE_FMT_LANGINFO ();
- if (!*format) {
- format = "%a %b %e %H:%M:%S %Z %Y";
+ 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 (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;
- }
+ if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &client, TOOL_NAME))) {
+ fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", ldret);
+ result = -1;
+ goto cleanup;
}
- else
- {
- ret = idevice_new(&phone, NULL);
- if (ret != IDEVICE_E_SUCCESS) {
- printf("No device found, is it plugged in?\n");
- return -1;
- }
+
+ if(lockdownd_get_value(client, NULL, "TimeIntervalSince1970", &node) != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "ERROR: Unable to retrieve 'TimeIntervalSince1970' node from device.\n");
+ result = -1;
+ goto cleanup;
}
- if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "idevicedate")) {
- idevice_free(phone);
- return -1;
+ if (node == NULL) {
+ fprintf(stderr, "ERROR: Empty node for 'TimeIntervalSince1970' received.\n");
+ result = -1;
+ goto cleanup;
}
+ node_type = plist_get_node_type(node);
+
/* get or set? */
if (setdate == 0) {
/* get time value from device */
- if(lockdownd_get_value(client, NULL, "TimeIntervalSince1970", &node) == LOCKDOWN_E_SUCCESS) {
- if (node) {
+ switch (node_type) {
+ case PLIST_UINT:
plist_get_uint_val(node, &datetime);
- plist_free(node);
- node = NULL;
+ break;
+ case PLIST_REAL:
+ {
+ double rv = 0;
+ plist_get_real_val(node, &rv);
+ datetime = rv;
+ }
+ break;
+ default:
+ fprintf(stderr, "ERROR: Unexpected node type for 'TimeIntervalSince1970'\n");
+ break;
+ }
+ plist_free(node);
+ node = NULL;
- /* date/time calculations */
- rawtime = (time_t)datetime;
- tmp = localtime(&rawtime);
+ /* date/time calculations */
+ rawtime = (time_t)datetime;
+ tmp = localtime(&rawtime);
- /* finally we format and print the current date */
- strftime(buffer, 80, format, tmp);
- puts(buffer);
- }
- }
+ /* finally we format and print the current date */
+ strftime(buffer, 80, DATE_FMT_LANGINFO, tmp);
+ puts(buffer);
} else {
datetime = setdate;
- if(lockdownd_set_value(client, NULL, "TimeIntervalSince1970", plist_new_uint(datetime)) == LOCKDOWN_E_SUCCESS) {
+ plist_free(node);
+ node = NULL;
+
+ switch (node_type) {
+ case PLIST_UINT:
+ node = plist_new_uint(datetime);
+ break;
+ case PLIST_REAL:
+ node = plist_new_real((double)datetime);
+ break;
+ default:
+ fprintf(stderr, "ERROR: Unexpected node type for 'TimeIntervalSince1970'\n");
+ break;
+ }
+
+ if(lockdownd_set_value(client, NULL, "TimeIntervalSince1970", node) == LOCKDOWN_E_SUCCESS) {
tmp = localtime(&setdate);
- strftime(buffer, 80, format, tmp);
+ strftime(buffer, 80, DATE_FMT_LANGINFO, tmp);
puts(buffer);
} else {
printf("ERROR: Failed to set date on device.\n");
}
+ node = NULL;
}
- lockdownd_client_free(client);
- idevice_free(phone);
+cleanup:
+ if (client)
+ lockdownd_client_free(client);
- return 0;
-}
+ if (device)
+ idevice_free(device);
+ return result;
+}
diff --git a/tools/idevicedebug.c b/tools/idevicedebug.c
new file mode 100644
index 0000000..36c594e
--- /dev/null
+++ b/tools/idevicedebug.c
@@ -0,0 +1,625 @@
+/*
+ * idevicedebug.c
+ * Interact with the debugserver service of a device.
+ *
+ * Copyright (c) 2014-2015 Martin Szulecki All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "idevicedebug"
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <getopt.h>
+
+#ifdef WIN32
+#include <windows.h>
+#define sleep(x) Sleep(x*1000)
+#endif
+
+#include <libimobiledevice/installation_proxy.h>
+#include <libimobiledevice/libimobiledevice.h>
+#include <libimobiledevice/debugserver.h>
+#include <plist/plist.h>
+#include "common/debug.h"
+
+static int debug_level = 0;
+
+#define log_debug(...) if (debug_level > 0) { printf(__VA_ARGS__); fputc('\n', stdout); }
+
+enum cmd_mode {
+ CMD_NONE = 0,
+ CMD_RUN,
+ CMD_KILL
+};
+
+static int quit_flag = 0;
+
+static void on_signal(int sig)
+{
+ fprintf(stderr, "Exiting...\n");
+ quit_flag++;
+}
+
+static int cancel_receive()
+{
+ return quit_flag;
+}
+
+static instproxy_error_t instproxy_client_get_object_by_key_from_info_dictionary_for_bundle_identifier(instproxy_client_t client, const char* appid, const char* key, plist_t* node)
+{
+ if (!client || !appid || !key)
+ return INSTPROXY_E_INVALID_ARG;
+
+ plist_t apps = NULL;
+
+ // create client options for any application types
+ plist_t client_opts = instproxy_client_options_new();
+ instproxy_client_options_add(client_opts, "ApplicationType", "Any", NULL);
+
+ // only return attributes we need
+ instproxy_client_options_set_return_attributes(client_opts, "CFBundleIdentifier", "CFBundleExecutable", key, NULL);
+
+ // only query for specific appid
+ const char* appids[] = {appid, NULL};
+
+ // query device for list of apps
+ instproxy_error_t ierr = instproxy_lookup(client, appids, client_opts, &apps);
+
+ instproxy_client_options_free(client_opts);
+
+ if (ierr != INSTPROXY_E_SUCCESS) {
+ return ierr;
+ }
+
+ plist_t app_found = plist_access_path(apps, 1, appid);
+ if (!app_found) {
+ if (apps)
+ plist_free(apps);
+ *node = NULL;
+ return INSTPROXY_E_OP_FAILED;
+ }
+
+ plist_t object = plist_dict_get_item(app_found, key);
+ if (object) {
+ *node = plist_copy(object);
+ } else {
+ log_debug("key %s not found", key);
+ return INSTPROXY_E_OP_FAILED;
+ }
+
+ plist_free(apps);
+
+ return INSTPROXY_E_SUCCESS;
+}
+
+static debugserver_error_t debugserver_client_handle_response(debugserver_client_t client, char** response, int* exit_status)
+{
+ debugserver_error_t dres = DEBUGSERVER_E_SUCCESS;
+ char* o = NULL;
+ char* r = *response;
+
+ /* Documentation of response codes can be found here:
+ https://github.com/llvm/llvm-project/blob/4fe839ef3a51e0ea2e72ea2f8e209790489407a2/lldb/docs/lldb-gdb-remote.txt#L1269
+ */
+
+ if (r[0] == 'O') {
+ /* stdout/stderr */
+ debugserver_decode_string(r + 1, strlen(r) - 1, &o);
+ printf("%s", o);
+ fflush(stdout);
+ } else if (r[0] == 'T') {
+ /* thread stopped information */
+ log_debug("Thread stopped. Details:\n%s", r + 1);
+ if (exit_status != NULL) {
+ /* "Thread stopped" seems to happen when assert() fails.
+ Use bash convention where signals cause an exit
+ status of 128 + signal
+ */
+ *exit_status = 128 + SIGABRT;
+ }
+ /* Break out of the loop. */
+ dres = DEBUGSERVER_E_UNKNOWN_ERROR;
+ } else if (r[0] == 'E') {
+ printf("ERROR: %s\n", r + 1);
+ } else if (r[0] == 'W' || r[0] == 'X') {
+ /* process exited */
+ debugserver_decode_string(r + 1, strlen(r) - 1, &o);
+ if (o != NULL) {
+ printf("Exit %s: %u\n", (r[0] == 'W' ? "status" : "due to signal"), o[0]);
+ if (exit_status != NULL) {
+ /* Use bash convention where signals cause an
+ exit status of 128 + signal
+ */
+ *exit_status = o[0] + (r[0] == 'W' ? 0 : 128);
+ }
+ } else {
+ log_debug("Unable to decode exit status from %s", r);
+ dres = DEBUGSERVER_E_UNKNOWN_ERROR;
+ }
+ } else if (r && strlen(r) == 0) {
+ log_debug("empty response");
+ } else {
+ log_debug("ERROR: unhandled response '%s'", r);
+ }
+
+ if (o != NULL) {
+ free(o);
+ o = NULL;
+ }
+
+ free(*response);
+ *response = NULL;
+ return dres;
+}
+
+static void print_usage(int argc, char **argv, int is_error)
+{
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] COMMAND\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Interact with the debugserver service of a device.\n"
+ "\n"
+ "Where COMMAND is one of:\n"
+ " run BUNDLEID [ARGS...] run app with BUNDLEID and optional ARGS on device.\n"
+ " kill BUNDLEID kill app with BUNDLEID\n"
+ "\n"
+ "The following OPTIONS are accepted:\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ " -n, --network connect to network device\n"
+ " --detach detach from app after launch, keeping it running\n"
+ " -e, --env NAME=VALUE set environment variable NAME to VALUE\n"
+ " -d, --debug enable communication debugging\n"
+ " -h, --help prints usage information\n"
+ " -v, --version prints version information\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
+}
+
+int main(int argc, char *argv[])
+{
+ int res = -1;
+ idevice_t device = NULL;
+ idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR;
+ instproxy_client_t instproxy_client = NULL;
+ debugserver_client_t debugserver_client = NULL;
+ int i;
+ int cmd = CMD_NONE;
+ const char* udid = NULL;
+ int use_network = 0;
+ int detach_after_start = 0;
+ const char* bundle_identifier = NULL;
+ char* path = NULL;
+ char* working_directory = NULL;
+ char **newlist = NULL;
+ char** environment = NULL;
+ int environment_index = 0;
+ int environment_count = 0;
+ char* response = NULL;
+ debugserver_command_t command = NULL;
+ debugserver_error_t dres = DEBUGSERVER_E_UNKNOWN_ERROR;
+
+ int c = 0;
+ const struct option longopts[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+ { "network", no_argument, NULL, 'n' },
+ { "detach", no_argument, NULL, 1 },
+ { "env", required_argument, NULL, 'e' },
+ { "version", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ /* map signals */
+ signal(SIGINT, on_signal);
+ signal(SIGTERM, on_signal);
+#ifndef WIN32
+ signal(SIGQUIT, on_signal);
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ while ((c = getopt_long(argc, argv, "dhu:ne:v", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ debug_level++;
+ if (debug_level > 1) {
+ idevice_set_debug_level(debug_level-1);
+ }
+ break;
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ udid = optarg;
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 1:
+ detach_after_start = 1;
+ break;
+ case 'e':
+ if (!*optarg || strchr(optarg, '=') == NULL) {
+ fprintf(stderr, "ERROR: environment variables need to be specified as -e KEY=VALUE\n");
+ print_usage(argc, argv, 1);
+ res = 2;
+ goto cleanup;
+ }
+ /* add environment variable */
+ if (!newlist)
+ newlist = malloc((environment_count + 1) * sizeof(char*));
+ else
+ newlist = realloc(environment, (environment_count + 1) * sizeof(char*));
+ newlist[environment_count++] = strdup(optarg);
+ environment = newlist;
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
+ res = 0;
+ goto cleanup;
+ break;
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
+ res = 0;
+ goto cleanup;
+ break;
+ default:
+ print_usage(argc, argv, 1);
+ res = 2;
+ goto cleanup;
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1) {
+ fprintf(stderr, "ERROR: Missing command.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+
+ if (!strcmp(argv[0], "run")) {
+ cmd = CMD_RUN;
+ if (argc < 2) {
+ /* make sure at least the bundle identifier was provided */
+ fprintf(stderr, "ERROR: Please supply the bundle identifier of the app to run.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ res = 2;
+ goto cleanup;
+ }
+ /* read bundle identifier */
+ bundle_identifier = argv[1];
+ i = 1;
+ } else if (!strcmp(argv[0], "kill")) {
+ cmd = CMD_KILL;
+ if (argc < 2) {
+ /* make sure at least the bundle identifier was provided */
+ fprintf(stderr, "ERROR: Please supply the bundle identifier of the app to run.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ res = 2;
+ goto cleanup;
+ }
+ /* read bundle identifier */
+ bundle_identifier = argv[1];
+ i = 1;
+ }
+
+ /* verify options */
+ if (cmd == CMD_NONE) {
+ fprintf(stderr, "ERROR: Unsupported command specified.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ res = 2;
+ goto cleanup;
+ }
+
+ if (environment) {
+ newlist = realloc(environment, (environment_count + 1) * sizeof(char*));
+ newlist[environment_count] = NULL;
+ environment = newlist;
+ }
+
+ /* connect to the device */
+ 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");
+ }
+ goto cleanup;
+ }
+
+ /* get the path to the app and it's working directory */
+ if (instproxy_client_start_service(device, &instproxy_client, TOOL_NAME) != INSTPROXY_E_SUCCESS) {
+ fprintf(stderr, "Could not start installation proxy service.\n");
+ goto cleanup;
+ }
+
+ instproxy_client_get_path_for_bundle_identifier(instproxy_client, bundle_identifier, &path);
+ if (!path) {
+ fprintf(stderr, "Invalid bundle identifier: %s\n", bundle_identifier);
+ goto cleanup;
+ }
+
+ plist_t container = NULL;
+ instproxy_client_get_object_by_key_from_info_dictionary_for_bundle_identifier(instproxy_client, bundle_identifier, "Container", &container);
+ instproxy_client_free(instproxy_client);
+ instproxy_client = NULL;
+
+ if (container && (plist_get_node_type(container) == PLIST_STRING)) {
+ plist_get_string_val(container, &working_directory);
+ log_debug("working_directory: %s\n", working_directory);
+ plist_free(container);
+ } else {
+ plist_free(container);
+ fprintf(stderr, "Could not determine container path for bundle identifier %s.\n", bundle_identifier);
+ goto cleanup;
+ }
+
+ /* start and connect to debugserver */
+ if (debugserver_client_start_service(device, &debugserver_client, TOOL_NAME) != DEBUGSERVER_E_SUCCESS) {
+ fprintf(stderr,
+ "Could not start com.apple.debugserver!\n"
+ "Please make sure to mount the developer disk image first:\n"
+ " 1) Get the iOS version from `ideviceinfo -k ProductVersion`.\n"
+ " 2) Find the matching iPhoneOS DeveloperDiskImage.dmg files.\n"
+ " 3) Run `ideviceimagemounter` with the above path.\n");
+ goto cleanup;
+ }
+
+ /* set receive params */
+ if (debugserver_client_set_receive_params(debugserver_client, cancel_receive, 250) != DEBUGSERVER_E_SUCCESS) {
+ fprintf(stderr, "Error in debugserver_client_set_receive_params\n");
+ goto cleanup;
+ }
+
+ /* enable logging for the session in debug mode */
+ if (debug_level) {
+ log_debug("Setting logging bitmask...");
+ debugserver_command_new("QSetLogging:bitmask=LOG_ALL|LOG_RNB_REMOTE|LOG_RNB_PACKETS;", 0, NULL, &command);
+ dres = debugserver_client_send_command(debugserver_client, command, &response, NULL);
+ debugserver_command_free(command);
+ command = NULL;
+ if (response) {
+ if (strncmp(response, "OK", 2) != 0) {
+ debugserver_client_handle_response(debugserver_client, &response, NULL);
+ goto cleanup;
+ }
+ free(response);
+ response = NULL;
+ }
+ }
+
+ /* set maximum packet size */
+ log_debug("Setting maximum packet size...");
+ char* packet_size[2] = { (char*)"1024", NULL};
+ debugserver_command_new("QSetMaxPacketSize:", 1, packet_size, &command);
+ dres = debugserver_client_send_command(debugserver_client, command, &response, NULL);
+ debugserver_command_free(command);
+ command = NULL;
+ if (response) {
+ if (strncmp(response, "OK", 2) != 0) {
+ debugserver_client_handle_response(debugserver_client, &response, NULL);
+ goto cleanup;
+ }
+ free(response);
+ response = NULL;
+ }
+
+ /* set working directory */
+ log_debug("Setting working directory...");
+ char* working_dir[2] = {working_directory, NULL};
+ debugserver_command_new("QSetWorkingDir:", 1, working_dir, &command);
+ dres = debugserver_client_send_command(debugserver_client, command, &response, NULL);
+ debugserver_command_free(command);
+ command = NULL;
+ if (response) {
+ if (strncmp(response, "OK", 2) != 0) {
+ debugserver_client_handle_response(debugserver_client, &response, NULL);
+ goto cleanup;
+ }
+ free(response);
+ response = NULL;
+ }
+
+ /* set environment */
+ if (environment) {
+ log_debug("Setting environment...");
+ for (environment_index = 0; environment_index < environment_count; environment_index++) {
+ log_debug("setting environment variable: %s", environment[environment_index]);
+ debugserver_client_set_environment_hex_encoded(debugserver_client, environment[environment_index], NULL);
+ }
+ }
+
+ /* set arguments and run app */
+ log_debug("Setting argv...");
+ i++; /* i is the offset of the bundle identifier, thus skip it */
+ int app_argc = (argc - i + 2);
+ char **app_argv = (char**)malloc(sizeof(char*) * app_argc);
+ app_argv[0] = path;
+ log_debug("app_argv[%d] = %s", 0, app_argv[0]);
+ app_argc = 1;
+ while (i < argc && argv && argv[i]) {
+ log_debug("app_argv[%d] = %s", app_argc, argv[i]);
+ app_argv[app_argc++] = argv[i];
+ i++;
+ }
+ app_argv[app_argc] = NULL;
+ debugserver_client_set_argv(debugserver_client, app_argc, app_argv, NULL);
+ free(app_argv);
+
+ /* check if launch succeeded */
+ log_debug("Checking if launch succeeded...");
+ debugserver_command_new("qLaunchSuccess", 0, NULL, &command);
+ dres = debugserver_client_send_command(debugserver_client, command, &response, NULL);
+ debugserver_command_free(command);
+ command = NULL;
+ if (response) {
+ if (strncmp(response, "OK", 2) != 0) {
+ debugserver_client_handle_response(debugserver_client, &response, NULL);
+ goto cleanup;
+ }
+ free(response);
+ response = NULL;
+ }
+
+ if (cmd == CMD_KILL) {
+ debugserver_command_new("k", 0, NULL, &command);
+ dres = debugserver_client_send_command(debugserver_client, command, &response, NULL);
+ debugserver_command_free(command);
+ command = NULL;
+ goto cleanup;
+ } else
+ if (cmd == CMD_RUN) {
+ if (detach_after_start) {
+ log_debug("Detaching from app");
+ debugserver_command_new("D", 0, NULL, &command);
+ dres = debugserver_client_send_command(debugserver_client, command, &response, NULL);
+ debugserver_command_free(command);
+ command = NULL;
+
+ res = (dres == DEBUGSERVER_E_SUCCESS) ? 0: -1;
+ goto cleanup;
+ }
+
+ /* set thread */
+ log_debug("Setting thread...");
+ debugserver_command_new("Hc0", 0, NULL, &command);
+ dres = debugserver_client_send_command(debugserver_client, command, &response, NULL);
+ debugserver_command_free(command);
+ command = NULL;
+ if (response) {
+ if (strncmp(response, "OK", 2) != 0) {
+ debugserver_client_handle_response(debugserver_client, &response, NULL);
+ goto cleanup;
+ }
+ free(response);
+ response = NULL;
+ }
+
+ /* continue running process */
+ log_debug("Continue running process...");
+ debugserver_command_new("c", 0, NULL, &command);
+ dres = debugserver_client_send_command(debugserver_client, command, &response, NULL);
+ debugserver_command_free(command);
+ command = NULL;
+ log_debug("Continue response: %s", response);
+
+ /* main loop which is parsing/handling packets during the run */
+ log_debug("Entering run loop...");
+ while (!quit_flag) {
+ if (dres != DEBUGSERVER_E_SUCCESS) {
+ log_debug("failed to receive response; error %d", dres);
+ break;
+ }
+
+ if (response) {
+ log_debug("response: %s", response);
+ if (strncmp(response, "OK", 2) != 0) {
+ dres = debugserver_client_handle_response(debugserver_client, &response, &res);
+ if (dres != DEBUGSERVER_E_SUCCESS) {
+ log_debug("failed to process response; error %d; %s", dres, response);
+ break;
+ }
+ }
+ }
+ if (res >= 0) {
+ goto cleanup;
+ }
+
+ dres = debugserver_client_receive_response(debugserver_client, &response, NULL);
+ }
+
+ /* ignore quit_flag after this point */
+ if (debugserver_client_set_receive_params(debugserver_client, NULL, 5000) != DEBUGSERVER_E_SUCCESS) {
+ fprintf(stderr, "Error in debugserver_client_set_receive_params\n");
+ goto cleanup;
+ }
+
+ /* interrupt execution */
+ debugserver_command_new("\x03", 0, NULL, &command);
+ dres = debugserver_client_send_command(debugserver_client, command, &response, NULL);
+ debugserver_command_free(command);
+ command = NULL;
+ if (response) {
+ if (strncmp(response, "OK", 2) != 0) {
+ debugserver_client_handle_response(debugserver_client, &response, NULL);
+ }
+ free(response);
+ response = NULL;
+ }
+
+ /* kill process after we finished */
+ log_debug("Killing process...");
+ debugserver_command_new("k", 0, NULL, &command);
+ dres = debugserver_client_send_command(debugserver_client, command, &response, NULL);
+ debugserver_command_free(command);
+ command = NULL;
+ if (response) {
+ if (strncmp(response, "OK", 2) != 0) {
+ debugserver_client_handle_response(debugserver_client, &response, NULL);
+ }
+ free(response);
+ response = NULL;
+ }
+
+ if (res < 0) {
+ res = (dres == DEBUGSERVER_E_SUCCESS) ? 0: -1;
+ }
+ }
+
+cleanup:
+ /* cleanup the house */
+ if (environment) {
+ for (environment_index = 0; environment_index < environment_count; environment_index++) {
+ free(environment[environment_index]);
+ }
+ free(environment);
+ }
+
+ if (working_directory)
+ free(working_directory);
+
+ if (path)
+ free(path);
+
+ if (response)
+ free(response);
+
+ if (debugserver_client)
+ debugserver_client_free(debugserver_client);
+
+ if (device)
+ idevice_free(device);
+
+ return res;
+}
diff --git a/tools/idevicedebugserverproxy.c b/tools/idevicedebugserverproxy.c
new file mode 100644
index 0000000..9fe7051
--- /dev/null
+++ b/tools/idevicedebugserverproxy.c
@@ -0,0 +1,375 @@
+/*
+ * idevicedebugserverproxy.c
+ * Proxy a debugserver connection from device for remote debugging
+ *
+ * Copyright (c) 2021 Nikias Bassen, All Rights Reserved.
+ * Copyright (c) 2012 Martin Szulecki All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "idevicedebugserverproxy"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <signal.h>
+#ifdef WIN32
+#include <winsock2.h>
+#include <windows.h>
+#else
+#include <sys/select.h>
+#endif
+
+#include <libimobiledevice/libimobiledevice.h>
+#include <libimobiledevice/debugserver.h>
+
+#include <libimobiledevice-glue/socket.h>
+#include <libimobiledevice-glue/thread.h>
+
+#ifndef ETIMEDOUT
+#define ETIMEDOUT 138
+#endif
+
+#define info(...) fprintf(stdout, __VA_ARGS__); fflush(stdout)
+#define debug(...) if(debug_mode) fprintf(stdout, __VA_ARGS__)
+
+static int support_lldb = 0;
+static int debug_mode = 0;
+static int quit_flag = 0;
+static uint16_t local_port = 0;
+
+typedef struct {
+ int client_fd;
+ idevice_t device;
+ debugserver_client_t debugserver_client;
+} socket_info_t;
+
+struct thread_info {
+ THREAD_T th;
+ int client_fd;
+ struct thread_info *next;
+};
+
+typedef struct thread_info thread_info_t;
+
+
+static void clean_exit(int sig)
+{
+ fprintf(stderr, "Exiting...\n");
+ quit_flag++;
+}
+
+static void print_usage(int argc, char **argv, int is_error)
+{
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] [PORT]\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Proxy debugserver connection from device to a local socket at PORT.\n"
+ "If PORT is omitted, the next available port will be used and printed\n"
+ "to stdout.\n"
+ "\n"
+ "OPTIONS:\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ " -n, --network connect to network device\n"
+ " -d, --debug enable communication debugging\n"
+ " -l, --lldb enable lldb support\n"
+ " -h, --help prints usage information\n"
+ " -v, --version prints version information\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
+}
+
+static int intercept_packet(char *packet, ssize_t *packet_len) {
+ static const char kReqLaunchServer[] = "$qLaunchGDBServer;#4b";
+
+ char buffer[64] = {0};
+ if (*packet_len == (ssize_t)(sizeof(kReqLaunchServer) - 1)
+ && memcmp(packet, kReqLaunchServer, sizeof(kReqLaunchServer) - 1) == 0) {
+ sprintf(buffer, "port:%d;", local_port);
+ } else {
+ return 0;
+ }
+ int sum = 0;
+ for (size_t i = 0; i < strlen(buffer); i++) {
+ sum += buffer[i];
+ }
+ sum = sum & 255;
+ sprintf(packet, "$%s#%02x", buffer, sum);
+ *packet_len = strlen(packet);
+ return 1;
+}
+
+static void* connection_handler(void* data)
+{
+ debugserver_error_t derr = DEBUGSERVER_E_SUCCESS;
+ socket_info_t* socket_info = (socket_info_t*)data;
+ const int bufsize = 65536;
+ char* buf;
+
+ int client_fd = socket_info->client_fd;
+
+ debug("%s: client_fd = %d\n", __func__, client_fd);
+
+ derr = debugserver_client_start_service(socket_info->device, &socket_info->debugserver_client, TOOL_NAME);
+ if (derr != DEBUGSERVER_E_SUCCESS) {
+ fprintf(stderr, "Could not start debugserver on device!\nPlease make sure to mount a developer disk image first.\n");
+ return NULL;
+ }
+
+ buf = malloc(bufsize);
+ if (!buf) {
+ fprintf(stderr, "Failed to allocate buffer\n");
+ return NULL;
+ }
+
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(client_fd, &fds);
+
+ int dtimeout = 1;
+
+ while (!quit_flag) {
+ ssize_t n = socket_receive_timeout(client_fd, buf, bufsize, 0, 1);
+ if (n != -ETIMEDOUT) {
+ if (n < 0) {
+ fprintf(stderr, "Failed to read from client fd: %s\n", strerror(-n));
+ break;
+ } else if (n == 0) {
+ fprintf(stderr, "connection closed\n");
+ break;
+ }
+ if (support_lldb && intercept_packet(buf, &n)) {
+ socket_send(client_fd, buf, n);
+ continue;
+ }
+ uint32_t sent = 0;
+ debugserver_client_send(socket_info->debugserver_client, buf, n, &sent);
+ }
+ do {
+ uint32_t r = 0;
+ derr = debugserver_client_receive_with_timeout(socket_info->debugserver_client, buf, bufsize, &r, dtimeout);
+ if (r > 0) {
+ socket_send(client_fd, buf, r);
+ dtimeout = 1;
+ } else if (derr == DEBUGSERVER_E_TIMEOUT) {
+ dtimeout = 5;
+ break;
+ } else {
+ fprintf(stderr, "debugserver connection closed\n");
+ break;
+ }
+ } while (derr == DEBUGSERVER_E_SUCCESS);
+ if (derr != DEBUGSERVER_E_TIMEOUT && derr != DEBUGSERVER_E_SUCCESS) {
+ break;
+ }
+ }
+ free(buf);
+
+ debug("%s: shutting down...\n", __func__);
+
+ debugserver_client_free(socket_info->debugserver_client);
+ socket_info->debugserver_client = NULL;
+
+ /* shutdown client socket */
+ socket_shutdown(socket_info->client_fd, SHUT_RDWR);
+ socket_close(socket_info->client_fd);
+
+ return NULL;
+}
+
+int main(int argc, char *argv[])
+{
+ idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR;
+ idevice_t device = NULL;
+ thread_info_t *thread_list = NULL;
+ const char* udid = NULL;
+ int use_network = 0;
+ int server_fd;
+ int result = EXIT_SUCCESS;
+ int c = 0;
+ const struct option longopts[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+ { "network", no_argument, NULL, 'n' },
+ { "lldb", no_argument, NULL, 'l' },
+ { "version", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0}
+ };
+
+#ifndef WIN32
+ struct sigaction sa;
+ struct sigaction si;
+ memset(&sa, '\0', sizeof(struct sigaction));
+ memset(&si, '\0', sizeof(struct sigaction));
+
+ sa.sa_handler = clean_exit;
+ sigemptyset(&sa.sa_mask);
+
+ si.sa_handler = SIG_IGN;
+ sigemptyset(&si.sa_mask);
+
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+ sigaction(SIGQUIT, &sa, NULL);
+ sigaction(SIGPIPE, &si, NULL);
+#else
+ /* bind signals */
+ signal(SIGINT, clean_exit);
+ signal(SIGTERM, clean_exit);
+#endif
+
+ /* parse cmdline arguments */
+ while ((c = getopt_long(argc, argv, "dhu:nv", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ debug_mode = 1;
+ idevice_set_debug_level(1);
+ socket_set_verbose(3);
+ break;
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID argument must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ udid = optarg;
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 'l':
+ support_lldb = 1;
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
+ return 0;
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
+ return 0;
+ default:
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argv[0] && (atoi(argv[0]) > 0)) {
+ local_port = atoi(argv[0]);
+ }
+
+ /* start services and connect to device */
+ ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX);
+ if (ret != IDEVICE_E_SUCCESS) {
+ if (udid) {
+ fprintf(stderr, "No device found with udid %s.\n", udid);
+ } else {
+ fprintf(stderr, "No device found.\n");
+ }
+ result = EXIT_FAILURE;
+ goto leave_cleanup;
+ }
+
+ /* create local socket */
+ server_fd = socket_create("127.0.0.1", local_port);
+ if (server_fd < 0) {
+ fprintf(stderr, "Could not create socket\n");
+ result = EXIT_FAILURE;
+ goto leave_cleanup;
+ }
+
+ if (local_port == 0) {
+ /* The user asked for any available port. Report the actual port. */
+ uint16_t port;
+ if (0 > socket_get_socket_port(server_fd, &port)) {
+ fprintf(stderr, "Could not determine socket port\n");
+ result = EXIT_FAILURE;
+ goto leave_cleanup;
+ }
+ printf("Listening on port %d\n", port);
+ }
+
+ while (!quit_flag) {
+ debug("%s: Waiting for connection on local port %d\n", __func__, local_port);
+
+ /* wait for client */
+ int client_fd = socket_accept(server_fd, local_port);
+ if (client_fd < 0) {
+ continue;
+ }
+
+ debug("%s: Handling new client connection...\n", __func__);
+
+ thread_info_t *el = (thread_info_t*)malloc(sizeof(thread_info_t));
+ if (!el) {
+ fprintf(stderr, "Out of memory\n");
+ exit(EXIT_FAILURE);
+ }
+ el->client_fd = client_fd;
+ el->next = NULL;
+
+ if (thread_list) {
+ thread_list->next = el;
+ } else {
+ thread_list = el;
+ }
+
+ socket_info_t *sinfo = (socket_info_t*)malloc(sizeof(socket_info_t));
+ if (!sinfo) {
+ fprintf(stderr, "Out of memory\n");
+ exit(EXIT_FAILURE);
+ }
+ sinfo->client_fd = client_fd;
+ sinfo->device = device;
+
+ if (thread_new(&(el->th), connection_handler, (void*)sinfo) != 0) {
+ fprintf(stderr, "Could not start connection handler.\n");
+ socket_shutdown(server_fd, SHUT_RDWR);
+ socket_close(server_fd);
+ break;
+ }
+ }
+
+ debug("%s: Shutting down debugserver proxy...\n", __func__);
+
+ /* join and clean up threads */
+ while (thread_list) {
+ thread_info_t *el = thread_list;
+ socket_shutdown(el->client_fd, SHUT_RDWR);
+ socket_close(el->client_fd);
+ thread_join(el->th);
+ thread_free(el->th);
+ thread_list = el->next;
+ free(el);
+ }
+
+leave_cleanup:
+ if (device) {
+ idevice_free(device);
+ }
+
+ return result;
+}
diff --git a/tools/idevicedevmodectl.c b/tools/idevicedevmodectl.c
new file mode 100644
index 0000000..bd1de6a
--- /dev/null
+++ b/tools/idevicedevmodectl.c
@@ -0,0 +1,462 @@
+/*
+ * idevicedevmodectl.c
+ * List or enable Developer Mode on iOS 16+ devices
+ *
+ * Copyright (c) 2022 Nikias Bassen, All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "idevicedevmodectl"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#ifndef WIN32
+#include <signal.h>
+#endif
+
+#ifdef WIN32
+#include <windows.h>
+#define __usleep(x) Sleep(x/1000)
+#else
+#include <arpa/inet.h>
+#include <unistd.h>
+#define __usleep(x) usleep(x)
+#endif
+
+#include <libimobiledevice/libimobiledevice.h>
+#include <libimobiledevice/lockdown.h>
+#include <libimobiledevice/property_list_service.h>
+#include <libimobiledevice-glue/utils.h>
+
+#define AMFI_LOCKDOWN_SERVICE_NAME "com.apple.amfi.lockdown"
+
+static char* udid = NULL;
+static int use_network = 0;
+
+static void print_usage(int argc, char **argv, int is_error)
+{
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] COMMAND\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Enable Developer Mode on iOS 16+ devices or print the current status.\n"
+ "\n"
+ "Where COMMAND is one of:\n"
+ " list Print the Developer Mode status of all connected devices\n"
+ " or for a specific one if --udid is given.\n"
+ " enable Enable Developer Mode (device will reboot),\n"
+ " and confirm it after device booted up again.\n"
+ "\n"
+ " arm Arm the Developer Mode (device will reboot)\n"
+ " confirm Confirm enabling of Developer Mode\n"
+ " reveal Reveal the Developer Mode menu on the device\n"
+ "\n"
+ "The following OPTIONS are accepted:\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ " -n, --network connect to network device\n"
+ " -d, --debug enable communication debugging\n"
+ " -h, --help print usage information\n"
+ " -v, --version print version information\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
+}
+
+enum {
+ OP_LIST,
+ OP_ENABLE,
+ OP_ARM,
+ OP_CONFIRM,
+ OP_REVEAL,
+ NUM_OPS
+};
+#define DEV_MODE_REVEAL 0
+#define DEV_MODE_ARM 1
+#define DEV_MODE_ENABLE 2
+
+static int get_developer_mode_status(const char* device_udid, int _use_network)
+{
+ idevice_error_t ret;
+ idevice_t device = NULL;
+ lockdownd_client_t lockdown = NULL;
+ lockdownd_error_t lerr = LOCKDOWN_E_UNKNOWN_ERROR;
+ plist_t val = NULL;
+
+ ret = idevice_new_with_options(&device, device_udid, (_use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX);
+ if (ret != IDEVICE_E_SUCCESS) {
+ return -1;
+ }
+
+ if (LOCKDOWN_E_SUCCESS != (lerr = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) {
+ idevice_free(device);
+ return -1;
+ }
+
+ lerr = lockdownd_get_value(lockdown, "com.apple.security.mac.amfi", "DeveloperModeStatus", &val);
+ if (lerr != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "ERROR: Could not get DeveloperModeStatus: %s\nPlease note that this feature is only available on iOS 16+.\n", lockdownd_strerror(lerr));
+ lockdownd_client_free(lockdown);
+ idevice_free(device);
+ return -2;
+ }
+
+ uint8_t dev_mode_status = 0;
+ plist_get_bool_val(val, &dev_mode_status);
+ plist_free(val);
+
+ lockdownd_client_free(lockdown);
+ idevice_free(device);
+
+ return dev_mode_status;
+}
+
+static int amfi_service_send_msg(property_list_service_client_t amfi, plist_t msg)
+{
+ int res;
+ property_list_service_error_t perr;
+
+ perr = property_list_service_send_xml_plist(amfi, plist_copy(msg));
+ if (perr != PROPERTY_LIST_SERVICE_E_SUCCESS) {
+ fprintf(stderr, "Could not send request to device: %d\n", perr);
+ res = 2;
+ } else {
+ plist_t reply = NULL;
+ perr = property_list_service_receive_plist(amfi, &reply);
+ if (perr == PROPERTY_LIST_SERVICE_E_SUCCESS) {
+ plist_t val = plist_dict_get_item(reply, "Error");
+ if (val) {
+ const char* err = plist_get_string_ptr(val, NULL);
+ fprintf(stderr, "Request failed: %s\n", err);
+ if (strstr(err, "passcode")) {
+ res = 2;
+ } else {
+ res = 1;
+ }
+ } else {
+ res = plist_dict_get_item(reply, "success") ? 0 : 1;
+ }
+ } else {
+ fprintf(stderr, "Could not receive reply from device: %d\n", perr);
+ res = 2;
+ }
+ plist_free(reply);
+ }
+ return res;
+}
+
+static int amfi_send_action(idevice_t device, unsigned int action)
+{
+ lockdownd_client_t lockdown = NULL;
+ lockdownd_service_descriptor_t service = NULL;
+ lockdownd_error_t lerr;
+
+ if (LOCKDOWN_E_SUCCESS != (lerr = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) {
+ fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", lerr);
+ return 1;
+ }
+
+ lerr = lockdownd_start_service(lockdown, AMFI_LOCKDOWN_SERVICE_NAME, &service);
+ if (lerr != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "Could not start service %s: %s\nPlease note that this feature is only available on iOS 16+.\n", AMFI_LOCKDOWN_SERVICE_NAME, lockdownd_strerror(lerr));
+ lockdownd_client_free(lockdown);
+ return 1;
+ }
+ lockdownd_client_free(lockdown);
+ lockdown = NULL;
+
+ property_list_service_client_t amfi = NULL;
+ if (property_list_service_client_new(device, service, &amfi) != PROPERTY_LIST_SERVICE_E_SUCCESS) {
+ fprintf(stderr, "Could not connect to %s on device\n", AMFI_LOCKDOWN_SERVICE_NAME);
+ if (service)
+ lockdownd_service_descriptor_free(service);
+ idevice_free(device);
+ return 1;
+ }
+ lockdownd_service_descriptor_free(service);
+
+ plist_t dict = plist_new_dict();
+ plist_dict_set_item(dict, "action", plist_new_uint(action));
+
+ int result = amfi_service_send_msg(amfi, dict);
+ plist_free(dict);
+
+ property_list_service_client_free(amfi);
+ amfi = NULL;
+
+ return result;
+}
+
+static int device_connected = 0;
+
+static void device_event_cb(const idevice_event_t* event, void* userdata)
+{
+ if (use_network && event->conn_type != CONNECTION_NETWORK) {
+ return;
+ }
+ if (!use_network && event->conn_type != CONNECTION_USBMUXD) {
+ return;
+ }
+ if (event->event == IDEVICE_DEVICE_ADD) {
+ if (!udid) {
+ udid = strdup(event->udid);
+ }
+ if (strcmp(udid, event->udid) == 0) {
+ device_connected = 1;
+ }
+ } else if (event->event == IDEVICE_DEVICE_REMOVE) {
+ if (strcmp(udid, event->udid) == 0) {
+ device_connected = 0;
+ }
+ }
+}
+
+
+#define WAIT_INTERVAL 200000
+#define WAIT_MAX(x) (x * (1000000 / WAIT_INTERVAL))
+#define WAIT_FOR(cond, timeout) { int __repeat = WAIT_MAX(timeout); while (!(cond) && __repeat-- > 0) { __usleep(WAIT_INTERVAL); } }
+
+int main(int argc, char *argv[])
+{
+ idevice_t device = NULL;
+ idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR;
+ lockdownd_client_t lockdown = NULL;
+ lockdownd_error_t lerr = LOCKDOWN_E_UNKNOWN_ERROR;
+ int res = 0;
+ int i;
+ int op = -1;
+ plist_t val = NULL;
+
+ int c = 0;
+ const struct option longopts[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+ { "network", no_argument, NULL, 'n' },
+ { "version", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0}
+ };
+
+#ifndef WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
+ /* parse cmdline args */
+ while ((c = getopt_long(argc, argv, "dhu:nv", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ idevice_set_debug_level(1);
+ break;
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID argument must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ udid = optarg;
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
+ return 0;
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
+ return 0;
+ default:
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!argv[0]) {
+ fprintf(stderr, "ERROR: Missing command.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+
+ i = 0;
+ if (!strcmp(argv[i], "list")) {
+ op = OP_LIST;
+ }
+ else if (!strcmp(argv[i], "enable")) {
+ op = OP_ENABLE;
+ }
+ else if (!strcmp(argv[i], "arm")) {
+ op = OP_ARM;
+ }
+ else if (!strcmp(argv[i], "confirm")) {
+ op = OP_CONFIRM;
+ }
+ else if (!strcmp(argv[i], "reveal")) {
+ op = OP_REVEAL;
+ }
+
+ if ((op == -1) || (op >= NUM_OPS)) {
+ fprintf(stderr, "ERROR: Unsupported command '%s'\n", argv[i]);
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+
+ if (op == OP_LIST) {
+ idevice_info_t *dev_list = NULL;
+
+ if (idevice_get_device_list_extended(&dev_list, &i) < 0) {
+ fprintf(stderr, "ERROR: Unable to retrieve device list!\n");
+ return -1;
+ }
+ if (i > 0) {
+ printf("%-40s %s\n", "Device", "DeveloperMode");
+ }
+ for (i = 0; dev_list[i] != NULL; i++) {
+ if (dev_list[i]->conn_type == CONNECTION_USBMUXD && use_network) continue;
+ if (dev_list[i]->conn_type == CONNECTION_NETWORK && !use_network) continue;
+ if (udid && (strcmp(dev_list[i]->udid, udid) != 0)) continue;
+ int mode = get_developer_mode_status(dev_list[i]->udid, use_network);
+ const char *mode_str = "N/A";
+ if (mode == 1) {
+ mode_str = "enabled";
+ } else if (mode == 0) {
+ mode_str = "disabled";
+ }
+ printf("%-40s %s\n", dev_list[i]->udid, mode_str);
+ }
+ idevice_device_list_extended_free(dev_list);
+
+ return 0;
+ }
+
+ idevice_subscription_context_t context = NULL;
+ idevice_events_subscribe(&context, device_event_cb, NULL);
+
+ WAIT_FOR(device_connected, 10);
+
+ 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 (LOCKDOWN_E_SUCCESS != (lerr = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) {
+ fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", lerr);
+ idevice_free(device);
+ return 1;
+ }
+
+ lerr = lockdownd_get_value(lockdown, "com.apple.security.mac.amfi", "DeveloperModeStatus", &val);
+ lockdownd_client_free(lockdown);
+ lockdown = NULL;
+ if (lerr != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "ERROR: Could not get DeveloperModeStatus: %s\nPlease note that this feature is only available on iOS 16+.\n", lockdownd_strerror(lerr));
+ idevice_free(device);
+ return 1;
+ }
+
+ uint8_t dev_mode_status = 0;
+ plist_get_bool_val(val, &dev_mode_status);
+
+ if ((op == OP_ENABLE || op == OP_ARM) && dev_mode_status) {
+ if (dev_mode_status) {
+ idevice_free(device);
+ printf("DeveloperMode is already enabled.\n");
+ return 0;
+ }
+ res = 0;
+ } else {
+ if (op == OP_ENABLE || op == OP_ARM) {
+ res = amfi_send_action(device, DEV_MODE_ARM);
+ if (res == 0) {
+ if (op == OP_ARM) {
+ printf("%s: Developer Mode armed, device will reboot now.\n", udid);
+ } else {
+ printf("%s: Developer Mode armed, waiting for reboot...\n", udid);
+
+ do {
+ // waiting for device to disconnect...
+ idevice_free(device);
+ device = NULL;
+ WAIT_FOR(!device_connected, 40);
+ if (device_connected) {
+ printf("%s: ERROR: Device didn't reboot?!\n", udid);
+ res = 2;
+ break;
+ }
+ printf("disconnected\n");
+
+ // waiting for device to reconnect...
+ WAIT_FOR(device_connected, 60);
+ if (!device_connected) {
+ printf("%s: ERROR: Device didn't re-connect?!\n", udid);
+ res = 2;
+ break;
+ }
+ printf("connected\n");
+
+ idevice_new(&device, udid);
+ res = amfi_send_action(device, DEV_MODE_ENABLE);
+ } while (0);
+ if (res == 0) {
+ printf("%s: Developer Mode successfully enabled.\n", udid);
+ } else {
+ printf("%s: Failed to enable developer mode (%d)\n", udid, res);
+ }
+ }
+ } else if (res == 2) {
+ amfi_send_action(device, DEV_MODE_REVEAL);
+ printf("%s: Developer Mode could not be enabled because the device has a passcode set. You have to enable it on the device itself under Settings -> Privacy & Security -> Developer Mode.\n", udid);
+ } else {
+ printf("%s: Failed to arm Developer Mode (%d)\n", udid, res);
+ }
+ } else if (op == OP_CONFIRM) {
+ res = amfi_send_action(device, DEV_MODE_ENABLE);
+ if (res == 0) {
+ printf("%s: Developer Mode successfully enabled.\n", udid);
+ } else {
+ printf("%s: Failed to enable Developer Mode (%d)\n", udid, res);
+ }
+ } else if (op == OP_REVEAL) {
+ res = amfi_send_action(device, DEV_MODE_REVEAL);
+ if (res == 0) {
+ printf("%s: Developer Mode menu revealed successfully.\n", udid);
+ } else {
+ printf("%s: Failed to reveal Developer Mode menu (%d)\n", udid, res);
+ }
+ }
+ }
+
+ idevice_free(device);
+
+ return res;
+}
diff --git a/tools/idevicediagnostics.c b/tools/idevicediagnostics.c
new file mode 100644
index 0000000..e699bc4
--- /dev/null
+++ b/tools/idevicediagnostics.c
@@ -0,0 +1,344 @@
+/*
+ * idevicediagnostics.c
+ * Retrieves diagnostics information from device
+ *
+ * Copyright (c) 2012 Martin Szulecki All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "idevicediagnostics"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <errno.h>
+#include <time.h>
+#ifndef WIN32
+#include <signal.h>
+#endif
+
+#include <libimobiledevice/libimobiledevice.h>
+#include <libimobiledevice/lockdown.h>
+#include <libimobiledevice/diagnostics_relay.h>
+
+enum cmd_mode {
+ CMD_NONE = 0,
+ CMD_SLEEP,
+ CMD_RESTART,
+ CMD_SHUTDOWN,
+ CMD_DIAGNOSTICS,
+ CMD_MOBILEGESTALT,
+ CMD_IOREGISTRY,
+ CMD_IOREGISTRY_ENTRY
+};
+
+static void print_xml(plist_t node)
+{
+ char *xml = NULL;
+ uint32_t len = 0;
+ plist_to_xml(node, &xml, &len);
+ if (xml) {
+ puts(xml);
+ }
+}
+
+static void print_usage(int argc, char **argv, int is_error)
+{
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] COMMAND\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Use diagnostics interface of a device running iOS 4 or later.\n"
+ "\n"
+ "Where COMMAND is one of:\n"
+ " diagnostics [TYPE] print diagnostics information from device by TYPE (All, WiFi, GasGauge, NAND)\n"
+ " mobilegestalt KEY [...] print mobilegestalt keys passed as arguments separated by a space.\n"
+ " ioreg [PLANE] print IORegistry of device, optionally by PLANE (IODeviceTree, IOPower, IOService) (iOS 5+ only)\n"
+ " ioregentry [KEY] print IORegistry entry of device (AppleARMPMUCharger, ASPStorage, ...) (iOS 5+ only)\n"
+ " shutdown shutdown device\n"
+ " restart restart device\n"
+ " sleep put device into sleep mode (disconnects from host)\n"
+ "\n"
+ "The following OPTIONS are accepted:\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ " -n, --network connect to network device\n"
+ " -d, --debug enable communication debugging\n"
+ " -h, --help prints usage information\n"
+ " -v, --version prints version information\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
+}
+
+int main(int argc, char **argv)
+{
+ idevice_t device = NULL;
+ lockdownd_client_t lockdown_client = NULL;
+ diagnostics_relay_client_t diagnostics_client = NULL;
+ lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
+ lockdownd_service_descriptor_t service = NULL;
+ int result = EXIT_FAILURE;
+ const char *udid = NULL;
+ int use_network = 0;
+ int cmd = CMD_NONE;
+ char* cmd_arg = NULL;
+ plist_t node = NULL;
+ plist_t keys = NULL;
+ int c = 0;
+ const struct option longopts[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+ { "network", no_argument, NULL, 'n' },
+ { "version", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0}
+ };
+
+#ifndef WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
+ /* parse cmdline args */
+ while ((c = getopt_long(argc, argv, "dhu:nv", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ idevice_set_debug_level(1);
+ break;
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID argument must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ udid = optarg;
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
+ return 0;
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
+ return 0;
+ default:
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!argv[0]) {
+ fprintf(stderr, "ERROR: No command specified\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+
+ if (!strcmp(argv[0], "sleep")) {
+ cmd = CMD_SLEEP;
+ }
+ else if (!strcmp(argv[0], "restart")) {
+ cmd = CMD_RESTART;
+ }
+ else if (!strcmp(argv[0], "shutdown")) {
+ cmd = CMD_SHUTDOWN;
+ }
+ else if (!strcmp(argv[0], "diagnostics")) {
+ cmd = CMD_DIAGNOSTICS;
+ /* read type */
+ if (!argv[1] || ((strcmp(argv[1], "All") != 0) && (strcmp(argv[1], "WiFi") != 0) && (strcmp(argv[1], "GasGauge") != 0) && (strcmp(argv[1], "NAND") != 0) && (strcmp(argv[1], "HDMI") != 0))) {
+ if (argv[1] == NULL) {
+ cmd_arg = strdup("All");
+ } else {
+ fprintf(stderr, "ERROR: Unknown TYPE %s\n", argv[1]);
+ print_usage(argc+optind, argv-optind, 1);
+ goto cleanup;
+ }
+ }
+ cmd_arg = strdup(argv[1]);
+ }
+ else if (!strcmp(argv[0], "mobilegestalt")) {
+ cmd = CMD_MOBILEGESTALT;
+ /* read keys */
+ if (!argv[1] || !*argv[1]) {
+ fprintf(stderr, "ERROR: Please supply the key to query.\n");
+ print_usage(argc, argv, 1);
+ goto cleanup;
+ }
+ int i = 1;
+ keys = plist_new_array();
+ while (argv[i] && *argv[i]) {
+ plist_array_append_item(keys, plist_new_string(argv[i]));
+ i++;
+ }
+ }
+ else if (!strcmp(argv[0], "ioreg")) {
+ cmd = CMD_IOREGISTRY;
+ /* read plane */
+ if (argv[1]) {
+ cmd_arg = strdup(argv[1]);
+ }
+ }
+ else if (!strcmp(argv[0], "ioregentry")) {
+ cmd = CMD_IOREGISTRY_ENTRY;
+ /* read key */
+ if (argv[1]) {
+ cmd_arg = strdup(argv[1]);
+ }
+ }
+
+ /* verify options */
+ if (cmd == CMD_NONE) {
+ fprintf(stderr, "ERROR: Unsupported command '%s'\n", argv[0]);
+ print_usage(argc+optind, argv-optind, 1);
+ goto cleanup;
+ }
+
+ if (IDEVICE_E_SUCCESS != idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX)) {
+ if (udid) {
+ printf("No device found with udid %s.\n", udid);
+ } else {
+ printf("No device found.\n");
+ }
+ goto cleanup;
+ }
+
+ if (LOCKDOWN_E_SUCCESS != (ret = lockdownd_client_new_with_handshake(device, &lockdown_client, TOOL_NAME))) {
+ idevice_free(device);
+ printf("ERROR: Could not connect to lockdownd, error code %d\n", ret);
+ goto cleanup;
+ }
+
+ /* attempt to use newer diagnostics service available on iOS 5 and later */
+ ret = lockdownd_start_service(lockdown_client, "com.apple.mobile.diagnostics_relay", &service);
+ if (ret == LOCKDOWN_E_INVALID_SERVICE) {
+ /* attempt to use older diagnostics service */
+ ret = lockdownd_start_service(lockdown_client, "com.apple.iosdiagnostics.relay", &service);
+ }
+ lockdownd_client_free(lockdown_client);
+
+ if (ret != LOCKDOWN_E_SUCCESS) {
+ idevice_free(device);
+ printf("ERROR: Could not start diagnostics relay service: %s\n", lockdownd_strerror(ret));
+ goto cleanup;
+ }
+
+ result = EXIT_FAILURE;
+
+ if ((ret == LOCKDOWN_E_SUCCESS) && service && (service->port > 0)) {
+ if (diagnostics_relay_client_new(device, service, &diagnostics_client) != DIAGNOSTICS_RELAY_E_SUCCESS) {
+ printf("ERROR: Could not connect to diagnostics_relay!\n");
+ } else {
+ switch (cmd) {
+ case CMD_SLEEP:
+ if (diagnostics_relay_sleep(diagnostics_client) == DIAGNOSTICS_RELAY_E_SUCCESS) {
+ printf("Putting device into deep sleep mode.\n");
+ result = EXIT_SUCCESS;
+ } else {
+ printf("ERROR: Failed to put device into deep sleep mode.\n");
+ }
+ break;
+ case CMD_RESTART:
+ if (diagnostics_relay_restart(diagnostics_client, DIAGNOSTICS_RELAY_ACTION_FLAG_WAIT_FOR_DISCONNECT) == DIAGNOSTICS_RELAY_E_SUCCESS) {
+ printf("Restarting device.\n");
+ result = EXIT_SUCCESS;
+ } else {
+ printf("ERROR: Failed to restart device.\n");
+ }
+ break;
+ case CMD_SHUTDOWN:
+ if (diagnostics_relay_shutdown(diagnostics_client, DIAGNOSTICS_RELAY_ACTION_FLAG_WAIT_FOR_DISCONNECT) == DIAGNOSTICS_RELAY_E_SUCCESS) {
+ printf("Shutting down device.\n");
+ result = EXIT_SUCCESS;
+ } else {
+ printf("ERROR: Failed to shutdown device.\n");
+ }
+ break;
+ case CMD_MOBILEGESTALT:
+ if (diagnostics_relay_query_mobilegestalt(diagnostics_client, keys, &node) == DIAGNOSTICS_RELAY_E_SUCCESS) {
+ if (node) {
+ print_xml(node);
+ result = EXIT_SUCCESS;
+ }
+ } else {
+ printf("ERROR: Unable to query mobilegestalt keys.\n");
+ }
+ break;
+ case CMD_IOREGISTRY_ENTRY:
+ if (diagnostics_relay_query_ioregistry_entry(diagnostics_client, cmd_arg == NULL ? "": cmd_arg, "", &node) == DIAGNOSTICS_RELAY_E_SUCCESS) {
+ if (node) {
+ print_xml(node);
+ result = EXIT_SUCCESS;
+ }
+ } else {
+ printf("ERROR: Unable to retrieve IORegistry from device.\n");
+ }
+ break;
+ case CMD_IOREGISTRY:
+ if (diagnostics_relay_query_ioregistry_plane(diagnostics_client, cmd_arg == NULL ? "": cmd_arg, &node) == DIAGNOSTICS_RELAY_E_SUCCESS) {
+ if (node) {
+ print_xml(node);
+ result = EXIT_SUCCESS;
+ }
+ } else {
+ printf("ERROR: Unable to retrieve IORegistry from device.\n");
+ }
+ break;
+ case CMD_DIAGNOSTICS:
+ default:
+ if (diagnostics_relay_request_diagnostics(diagnostics_client, cmd_arg, &node) == DIAGNOSTICS_RELAY_E_SUCCESS) {
+ if (node) {
+ print_xml(node);
+ result = EXIT_SUCCESS;
+ }
+ } else {
+ printf("ERROR: Unable to retrieve diagnostics from device.\n");
+ }
+ break;
+ }
+
+ diagnostics_relay_goodbye(diagnostics_client);
+ diagnostics_relay_client_free(diagnostics_client);
+ }
+ } else {
+ printf("ERROR: Could not start diagnostics service!\n");
+ }
+
+ if (service) {
+ lockdownd_service_descriptor_free(service);
+ service = NULL;
+ }
+
+ idevice_free(device);
+
+cleanup:
+ if (node) {
+ plist_free(node);
+ }
+ if (keys) {
+ plist_free(keys);
+ }
+ if (cmd_arg) {
+ free(cmd_arg);
+ }
+ return result;
+}
diff --git a/tools/ideviceenterrecovery.c b/tools/ideviceenterrecovery.c
index 827946b..29cc5c9 100644
--- a/tools/ideviceenterrecovery.c
+++ b/tools/ideviceenterrecovery.c
@@ -8,86 +8,132 @@
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "ideviceenterrecovery"
+
#include <stdio.h>
#include <string.h>
-#include <errno.h>
#include <stdlib.h>
+#include <getopt.h>
+#include <errno.h>
+#ifndef WIN32
+#include <signal.h>
+#endif
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/lockdown.h>
-static void print_usage(int argc, char **argv)
+static void print_usage(int argc, char **argv, int is_error)
{
- char *name = NULL;
-
- name = strrchr(argv[0], '/');
- printf("Usage: %s [OPTIONS] UUID\n", (name ? name + 1: argv[0]));
- printf("Makes a device with the supplied 40-digit UUID enter recovery mode immediately.\n\n");
- printf(" -d, --debug\t\tenable communication debugging\n");
- printf(" -h, --help\t\tprints usage information\n");
- printf("\n");
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] UDID\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Makes a device with the supplied UDID enter recovery mode immediately.\n"
+ "\n"
+ "OPTIONS:\n"
+ " -d, --debug enable communication debugging\n"
+ " -h, --help prints usage information\n"
+ " -v, --version prints version information\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
}
int main(int argc, char *argv[])
{
lockdownd_client_t client = NULL;
- idevice_t phone = NULL;
+ lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR;
+ idevice_t device = NULL;
idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR;
- int i;
- char uuid[41];
- uuid[0] = 0;
+ const char* udid = NULL;
+ int c = 0;
+ const struct option longopts[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0}
+ };
+#ifndef WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
/* parse cmdline args */
- for (i = 1; i < argc; i++) {
- if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) {
+ while ((c = getopt_long(argc, argv, "dhv", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
idevice_set_debug_level(1);
- continue;
- }
- else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
- print_usage(argc, argv);
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
+ return 0;
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
return 0;
+ default:
+ print_usage(argc, argv, 1);
+ return 2;
}
}
+ argc -= optind;
+ argv += optind;
- i--;
- if (!argv[i] || (strlen(argv[i]) != 40)) {
- print_usage(argc, argv);
- return 0;
+ if (!argv[0]) {
+ fprintf(stderr, "ERROR: No UDID specified\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
}
- strcpy(uuid, argv[i]);
+ udid = argv[0];
- ret = idevice_new(&phone, uuid);
+ ret = idevice_new(&device, udid);
if (ret != IDEVICE_E_SUCCESS) {
- printf("No device found with uuid %s, is it plugged in?\n", uuid);
- return -1;
+ printf("No device found with udid %s.\n", udid);
+ return 1;
}
- if (LOCKDOWN_E_SUCCESS != lockdownd_client_new(phone, &client, "ideviceenterrecovery")) {
- idevice_free(phone);
- return -1;
+ if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new(device, &client, TOOL_NAME))) {
+ printf("ERROR: Could not connect to lockdownd: %s (%d)\n", lockdownd_strerror(ldret), ldret);
+ idevice_free(device);
+ return 1;
}
- /* run query and output information */
- printf("Telling device with uuid %s to enter recovery mode.\n", uuid);
- if(lockdownd_enter_recovery(client) != LOCKDOWN_E_SUCCESS)
- {
+ int res = 0;
+ printf("Telling device with udid %s to enter recovery mode.\n", udid);
+ ldret = lockdownd_enter_recovery(client);
+ if (ldret == LOCKDOWN_E_SESSION_INACTIVE) {
+ lockdownd_client_free(client);
+ client = NULL;
+ if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &client, TOOL_NAME))) {
+ printf("ERROR: Could not connect to lockdownd: %s (%d)\n", lockdownd_strerror(ldret), ldret);
+ idevice_free(device);
+ return 1;
+ }
+ ldret = lockdownd_enter_recovery(client);
+ }
+ if (ldret != LOCKDOWN_E_SUCCESS) {
printf("Failed to enter recovery mode.\n");
+ res = 1;
+ } else {
+ printf("Device is successfully switching to recovery mode.\n");
}
- printf("Device is successfully switching to recovery mode.\n");
lockdownd_client_free(client);
- idevice_free(phone);
+ idevice_free(device);
- return 0;
+ return res;
}
diff --git a/tools/ideviceimagemounter.c b/tools/ideviceimagemounter.c
index 3d299f8..511583e 100644
--- a/tools/ideviceimagemounter.c
+++ b/tools/ideviceimagemounter.c
@@ -1,26 +1,30 @@
-/**
- * ideviceimagemounter -- Mount developer/debug disk images on the iPhone/iPod
+/*
+ * ideviceimagemounter.c
+ * Mount developer/debug disk images on the device
*
* Copyright (C) 2010 Nikias Bassen <nikias@gmx.li>
*
- * Licensed under the GNU General Public License Version 2
+ * 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 program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
+ * 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 General Public License for more profile.
+ * 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 General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
- * USA
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "ideviceimagemounter"
+
#include <stdlib.h>
#define _GNU_SOURCE 1
#define __USE_GNU 1
@@ -28,297 +32,311 @@
#include <string.h>
#include <getopt.h>
#include <errno.h>
-#include <glib.h>
#include <libgen.h>
+#include <time.h>
+#include <sys/time.h>
+#include <inttypes.h>
+#ifndef WIN32
+#include <signal.h>
+#endif
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/lockdown.h>
#include <libimobiledevice/afc.h>
#include <libimobiledevice/notification_proxy.h>
#include <libimobiledevice/mobile_image_mounter.h>
-
-static int indent_level = 0;
+#include <libimobiledevice-glue/sha.h>
+#include <libimobiledevice-glue/utils.h>
+#include <asprintf.h>
+#include <plist/plist.h>
+#include <libtatsu/tss.h>
static int list_mode = 0;
+static int use_network = 0;
static int xml_mode = 0;
-static char *uuid = NULL;
-static char *imagetype = NULL;
+static const char *udid = NULL;
+static const char *imagetype = NULL;
static const char PKG_PATH[] = "PublicStaging";
static const char PATH_PREFIX[] = "/private/var/mobile/Media";
-static void print_usage(int argc, char **argv)
+typedef enum {
+ DISK_IMAGE_UPLOAD_TYPE_AFC,
+ DISK_IMAGE_UPLOAD_TYPE_UPLOAD_IMAGE
+} disk_image_upload_type_t;
+
+enum cmd_mode {
+ CMD_NONE = 0,
+ CMD_MOUNT,
+ CMD_UNMOUNT,
+ CMD_LIST,
+ CMD_DEVMODESTATUS
+};
+
+int cmd = CMD_NONE;
+
+static void print_usage(int argc, char **argv, int is_error)
{
- char *name = NULL;
-
- name = strrchr(argv[0], '/');
- printf("Usage: %s [OPTIONS] IMAGE_FILE IMAGE_SIGNATURE_FILE\n\n", (name ? name + 1: argv[0]));
- printf("Mounts the specified disk image on the device.\n\n");
- printf(" -u, --uuid UUID\ttarget specific device by its 40-digit device UUID\n");
- printf(" -l, --list\t\tList mount information\n");
- printf(" -t, --imagetype\tImage type to use, default is 'Developer'\n");
- printf(" -x, --xml\t\tUse XML output\n");
- printf(" -d, --debug\t\tenable communication debugging\n");
- printf(" -h, --help\t\tprints usage information\n");
- printf("\n");
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] COMMAND [COMMAND OPTIONS...]\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Mount, list, or unmount a disk image on the device.\n"
+ "\n"
+ "COMMANDS:\n"
+ " mount PATH Mount the developer disk image at PATH.\n"
+ " For iOS 17+, PATH is a directory containing a .dmg image,\n"
+ " a BuildManifest.plist, and a Firmware sub-directory;\n"
+ " for older versions PATH is a .dmg filename with a"
+ " .dmg.signature in the same directory, or with another\n"
+ " parameter pointing to a file elsewhere.\n"
+ " list List mounted disk images.\n"
+ " unmount PATH Unmount the image mounted at PATH.\n"
+ " devmodestatus Query the developer mode status (iOS 16+)\n"
+ "\n"
+ "OPTIONS:\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ " -n, --network connect to network device\n"
+ " -t, --imagetype TYPE Image type to use, default is 'Developer'\n"
+ " -x, --xml Use XML output\n"
+ " -d, --debug enable communication debugging\n"
+ " -h, --help prints usage information\n"
+ " -v, --version prints version information\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
}
static void parse_opts(int argc, char **argv)
{
+ int debug_level = 0;
static struct option longopts[] = {
- {"help", 0, NULL, 'h'},
- {"uuid", 0, NULL, 'u'},
- {"list", 0, NULL, 'l'},
- {"imagetype", 0, NULL, 't'},
- {"xml", 0, NULL, 'x'},
- {"debug", 0, NULL, 'd'},
- {NULL, 0, NULL, 0}
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+ { "network", no_argument, NULL, 'n' },
+ { "imagetype", required_argument, NULL, 't' },
+ { "xml", no_argument, NULL, 'x' },
+ { "debug", no_argument, NULL, 'd' },
+ { "version", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0 }
};
int c;
while (1) {
- c = getopt_long(argc, argv, "hu:lt:xd", longopts,
- (int *) 0);
+ c = getopt_long(argc, argv, "hu:t:xdnv", longopts, NULL);
if (c == -1) {
break;
}
switch (c) {
case 'h':
- print_usage(argc, argv);
+ print_usage(argc, argv, 0);
exit(0);
case 'u':
- if (strlen(optarg) != 40) {
- printf("%s: invalid UUID specified (length != 40)\n",
- argv[0]);
- print_usage(argc, argv);
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID must not be empty!\n");
+ print_usage(argc, argv, 1);
exit(2);
}
- uuid = strdup(optarg);
+ udid = optarg;
break;
- case 'l':
- list_mode = 1;
+ case 'n':
+ use_network = 1;
break;
case 't':
- imagetype = strdup(optarg);
+ imagetype = optarg;
break;
case 'x':
xml_mode = 1;
break;
case 'd':
- idevice_set_debug_level(1);
+ debug_level++;
break;
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
+ exit(0);
default:
- print_usage(argc, argv);
+ print_usage(argc, argv, 1);
exit(2);
}
}
+ idevice_set_debug_level(debug_level);
+ tss_set_debug_level(debug_level);
}
-static void plist_node_to_string(plist_t node);
-
-static void plist_array_to_string(plist_t node)
+static ssize_t mim_upload_cb(void* buf, size_t size, void* userdata)
{
- /* iterate over items */
- int i, count;
- plist_t subnode = NULL;
-
- count = plist_array_get_size(node);
-
- for (i = 0; i < count; i++) {
- subnode = plist_array_get_item(node, i);
- printf("%*s", indent_level, "");
- printf("%d: ", i);
- plist_node_to_string(subnode);
- }
-}
-
-static void plist_dict_to_string(plist_t node)
-{
- /* iterate over key/value pairs */
- plist_dict_iter it = NULL;
-
- char* key = NULL;
- plist_t subnode = NULL;
- plist_dict_new_iter(node, &it);
- plist_dict_next_item(node, it, &key, &subnode);
- while (subnode)
- {
- printf("%*s", indent_level, "");
- printf("%s", key);
- if (plist_get_node_type(subnode) == PLIST_ARRAY)
- printf("[%d]: ", plist_array_get_size(subnode));
- else
- printf(": ");
- free(key);
- key = NULL;
- plist_node_to_string(subnode);
- plist_dict_next_item(node, it, &key, &subnode);
- }
- free(it);
-}
-
-static void plist_node_to_string(plist_t node)
-{
- char *s = NULL;
- char *data = NULL;
- double d;
- uint8_t b;
- uint64_t u = 0;
- GTimeVal tv = { 0, 0 };
-
- plist_type t;
-
- if (!node)
- return;
-
- t = plist_get_node_type(node);
-
- switch (t) {
- case PLIST_BOOLEAN:
- plist_get_bool_val(node, &b);
- printf("%s\n", (b ? "true" : "false"));
- break;
-
- case PLIST_UINT:
- plist_get_uint_val(node, &u);
- printf("%llu\n", (long long)u);
- break;
-
- case PLIST_REAL:
- plist_get_real_val(node, &d);
- printf("%f\n", d);
- break;
-
- case PLIST_STRING:
- plist_get_string_val(node, &s);
- printf("%s\n", s);
- free(s);
- break;
-
- case PLIST_KEY:
- plist_get_key_val(node, &s);
- printf("%s: ", s);
- free(s);
- break;
-
- case PLIST_DATA:
- plist_get_data_val(node, &data, &u);
- uint64_t i;
- for (i = 0; i < u; i++) {
- printf("%02x", (unsigned char)data[i]);
- }
- free(data);
- printf("\n");
- g_free(s);
- break;
-
- case PLIST_DATE:
- plist_get_date_val(node, (int32_t*)&tv.tv_sec, (int32_t*)&tv.tv_usec);
- s = g_time_val_to_iso8601(&tv);
- printf("%s\n", s);
- free(s);
- break;
-
- case PLIST_ARRAY:
- printf("\n");
- indent_level++;
- plist_array_to_string(node);
- indent_level--;
- break;
-
- case PLIST_DICT:
- printf("\n");
- indent_level++;
- plist_dict_to_string(node);
- indent_level--;
- break;
-
- default:
- break;
- }
-}
-
-static void print_xml(plist_t node)
-{
- char *xml = NULL;
- uint32_t len = 0;
- plist_to_xml(node, &xml, &len);
- if (xml)
- puts(xml);
+ return fread(buf, 1, size, (FILE*)userdata);
}
int main(int argc, char **argv)
{
idevice_t device = NULL;
lockdownd_client_t lckd = NULL;
+ lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR;
mobile_image_mounter_client_t mim = NULL;
afc_client_t afc = NULL;
- uint16_t port = 0;
+ lockdownd_service_descriptor_t service = NULL;
int res = -1;
char *image_path = NULL;
+ size_t image_size = 0;
char *image_sig_path = NULL;
+#ifndef WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
parse_opts(argc, argv);
argc -= optind;
argv += optind;
- if (!list_mode) {
- if (argc < 1) {
- printf("ERROR: No IMAGE_FILE has been given!\n");
- return -1;
- }
- image_path = strdup(argv[0]);
- if (argc >= 2) {
- image_sig_path = strdup(argv[1]);
+ if (argc == 0) {
+ fprintf(stderr, "ERROR: Missing command.\n\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+
+ char* cmdstr = argv[0];
+
+ int optind2 = 0;
+ if (!strcmp(cmdstr, "mount")) {
+ cmd = CMD_MOUNT;
+ optind2++;
+ } else if (!strcmp(cmdstr, "list")) {
+ cmd = CMD_LIST;
+ optind2++;
+ } else if (!strcmp(cmdstr, "umount") || !strcmp(cmdstr, "unmount")) {
+ cmd = CMD_UNMOUNT;
+ optind2++;
+ } else if (!strcmp(cmdstr, "devmodestatus")) {
+ cmd = CMD_DEVMODESTATUS;
+ optind2++;
+ } else {
+ // assume mount command, unless -l / --list was specified
+ if (list_mode) {
+ cmd = CMD_LIST;
} else {
- if (asprintf(&image_sig_path, "%s.signature", image_path) < 0) {
- printf("Out of memory?!\n");
- return -1;
- }
+ cmd = CMD_MOUNT;
}
}
- if (IDEVICE_E_SUCCESS != idevice_new(&device, uuid)) {
- printf("No device found, is it plugged in?\n");
- return -1;
+ argc -= optind2;
+ argv += optind2;
+ optind += optind2;
+
+ switch (cmd) {
+ case CMD_MOUNT:
+ if (argc < 1) {
+ fprintf(stderr, "ERROR: Missing IMAGE_FILE for mount command\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+ image_path = strdup(argv[0]);
+ if (argc >= 2) {
+ image_sig_path = strdup(argv[1]);
+ } else {
+ if (asprintf(&image_sig_path, "%s.signature", image_path) < 0) {
+ printf("Out of memory?!\n");
+ return 1;
+ }
+ }
+ break;
+ case CMD_UNMOUNT:
+ if (argc != 1) {
+ fprintf(stderr, "ERROR: Missing mount path (argc = %d)\n", argc);
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (IDEVICE_E_SUCCESS != idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX)) {
+ if (udid) {
+ printf("No device found with udid %s.\n", udid);
+ } else {
+ printf("No device found.\n");
+ }
+ return 1;
}
- if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(device, &lckd, "ideviceimagemounter")) {
- printf("ERROR: could not connect to lockdown. Exiting.\n");
+ if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &lckd, TOOL_NAME))) {
+ printf("ERROR: Could not connect to lockdown, error code %d.\n", ldret);
goto leave;
}
- lockdownd_start_service(lckd, "com.apple.mobile.mobile_image_mounter", &port);
+ plist_t pver = NULL;
+ char *product_version = NULL;
+ lockdownd_get_value(lckd, NULL, "ProductVersion", &pver);
+ if (pver && plist_get_node_type(pver) == PLIST_STRING) {
+ plist_get_string_val(pver, &product_version);
+ }
+ disk_image_upload_type_t disk_image_upload_type = DISK_IMAGE_UPLOAD_TYPE_AFC;
+ int product_version_major = 0;
+ int product_version_minor = 0;
+ if (product_version) {
+ if (sscanf(product_version, "%d.%d.%*d", &product_version_major, &product_version_minor) == 2) {
+ if (product_version_major >= 7)
+ disk_image_upload_type = DISK_IMAGE_UPLOAD_TYPE_UPLOAD_IMAGE;
+ }
+ }
- if (port == 0) {
+ if (product_version_major >= 16) {
+ uint8_t dev_mode_status = 0;
+ plist_t val = NULL;
+ ldret = lockdownd_get_value(lckd, "com.apple.security.mac.amfi", "DeveloperModeStatus", &val);
+ if (ldret == LOCKDOWN_E_SUCCESS) {
+ plist_get_bool_val(val, &dev_mode_status);
+ plist_free(val);
+ }
+ if (!dev_mode_status) {
+ printf("ERROR: You have to enable Developer Mode on the given device in order to allowing mounting a developer disk image.\n");
+ goto leave;
+ }
+ }
+
+ lockdownd_start_service(lckd, "com.apple.mobile.mobile_image_mounter", &service);
+
+ if (!service || service->port == 0) {
printf("ERROR: Could not start mobile_image_mounter service!\n");
goto leave;
}
- if (mobile_image_mounter_new(device, port, &mim) != MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
+ if (mobile_image_mounter_new(device, service, &mim) != MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
printf("ERROR: Could not connect to mobile_image_mounter!\n");
goto leave;
- }
+ }
+
+ if (service) {
+ lockdownd_service_descriptor_free(service);
+ service = NULL;
+ }
- if (!list_mode) {
+ if (cmd == CMD_MOUNT) {
struct stat fst;
- port = 0;
- if ((lockdownd_start_service(lckd, "com.apple.afc", &port) !=
- LOCKDOWN_E_SUCCESS) || !port) {
- fprintf(stderr, "Could not start com.apple.afc!\n");
- goto leave;
- }
- if (afc_client_new(device, port, &afc) != AFC_E_SUCCESS) {
- fprintf(stderr, "Could not connect to AFC!\n");
- goto leave;
+ if (disk_image_upload_type == DISK_IMAGE_UPLOAD_TYPE_AFC) {
+ if ((lockdownd_start_service(lckd, "com.apple.afc", &service) !=
+ LOCKDOWN_E_SUCCESS) || !service || !service->port) {
+ fprintf(stderr, "Could not start com.apple.afc!\n");
+ goto leave;
+ }
+ if (afc_client_new(device, service, &afc) != AFC_E_SUCCESS) {
+ fprintf(stderr, "Could not connect to AFC!\n");
+ goto leave;
+ }
+ if (service) {
+ lockdownd_service_descriptor_free(service);
+ service = NULL;
+ }
}
if (stat(image_path, &fst) != 0) {
fprintf(stderr, "ERROR: stat: %s: %s\n", image_path, strerror(errno));
goto leave;
}
- if (stat(image_sig_path, &fst) != 0) {
+ image_size = fst.st_size;
+ if (product_version_major < 17 && stat(image_sig_path, &fst) != 0) {
fprintf(stderr, "ERROR: stat: %s: %s\n", image_sig_path, strerror(errno));
goto leave;
}
@@ -327,49 +345,240 @@ int main(int argc, char **argv)
lockdownd_client_free(lckd);
lckd = NULL;
- mobile_image_mounter_error_t err;
+ mobile_image_mounter_error_t err = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR;
plist_t result = NULL;
- if (list_mode) {
+ if (cmd == CMD_LIST) {
/* list mounts mode */
if (!imagetype) {
- imagetype = strdup("Developer");
+ if (product_version_major < 17) {
+ imagetype = "Developer";
+ } else {
+ imagetype = "Personalized";
+ }
}
err = mobile_image_mounter_lookup_image(mim, imagetype, &result);
- free(imagetype);
if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
res = 0;
- if (xml_mode) {
- print_xml(result);
- } else {
- plist_dict_to_string(result);
- }
+ plist_write_to_stream(result, stdout, (xml_mode) ? PLIST_FORMAT_XML : PLIST_FORMAT_LIMD, 0);
} else {
printf("Error: lookup_image returned %d\n", err);
}
- } else {
- char sig[8192];
+ } else if (cmd == CMD_MOUNT) {
+ unsigned char *sig = NULL;
size_t sig_length = 0;
- FILE *f = fopen(image_sig_path, "r");
- if (!f) {
- fprintf(stderr, "Error opening signature file '%s': %s\n", image_sig_path, strerror(errno));
- goto leave;
- }
- sig_length = fread(sig, 1, sizeof(sig), f);
- fclose(f);
- if (sig_length == 0) {
- fprintf(stderr, "Could not read signature from file '%s'\n", image_sig_path);
- goto leave;
- }
+ FILE *f;
+ struct stat fst;
+ plist_t mount_options = NULL;
- f = fopen(image_path, "r");
- if (!f) {
- fprintf(stderr, "Error opening image file '%s': %s\n", image_path, strerror(errno));
- goto leave;
+ if (product_version_major < 17) {
+ f = fopen(image_sig_path, "rb");
+ if (!f) {
+ fprintf(stderr, "Error opening signature file '%s': %s\n", image_sig_path, strerror(errno));
+ goto leave;
+ }
+ if (fstat(fileno(f), &fst) != 0) {
+ fprintf(stderr, "Error: fstat: %s\n", strerror(errno));
+ goto leave;
+ }
+ sig = malloc(fst.st_size);
+ sig_length = fread(sig, 1, fst.st_size, f);
+ fclose(f);
+ if (sig_length == 0) {
+ fprintf(stderr, "Could not read signature from file '%s'\n", image_sig_path);
+ goto leave;
+ }
+
+ f = fopen(image_path, "rb");
+ if (!f) {
+ fprintf(stderr, "Error opening image file '%s': %s\n", image_path, strerror(errno));
+ goto leave;
+ }
+ } else {
+ if (stat(image_path, &fst) != 0) {
+ fprintf(stderr, "Error: stat: '%s': %s\n", image_path, strerror(errno));
+ goto leave;
+ }
+ if (!S_ISDIR(fst.st_mode)) {
+ fprintf(stderr, "Error: Personalized Disk Image mount expects a directory as image path.\n");
+ goto leave;
+ }
+ char* build_manifest_path = string_build_path(image_path, "BuildManifest.plist", NULL);
+ plist_t build_manifest = NULL;
+ if (plist_read_from_file(build_manifest_path, &build_manifest, NULL) != 0) {
+ free(build_manifest_path);
+ build_manifest_path = string_build_path(image_path, "Restore", "BuildManifest.plist", NULL);
+ if (plist_read_from_file(build_manifest_path, &build_manifest, NULL) == 0) {
+ char* image_path_new = string_build_path(image_path, "Restore", NULL);
+ free(image_path);
+ image_path = image_path_new;
+ }
+ }
+ if (!build_manifest) {
+ fprintf(stderr, "Error: Could not locate BuildManifest.plist inside given disk image path!\n");
+ goto leave;
+ }
+
+ plist_t identifiers = NULL;
+ mobile_image_mounter_error_t merr = mobile_image_mounter_query_personalization_identifiers(mim, NULL, &identifiers);
+ if (merr != MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
+ fprintf(stderr, "Failed to query personalization identifiers: %d\n", merr);
+ goto error_out;
+ }
+
+ unsigned int board_id = plist_dict_get_uint(identifiers, "BoardId");
+ unsigned int chip_id = plist_dict_get_uint(identifiers, "ChipID");
+
+ plist_t build_identities = plist_dict_get_item(build_manifest, "BuildIdentities");
+ plist_array_iter iter;
+ plist_array_new_iter(build_identities, &iter);
+ plist_t item = NULL;
+ plist_t build_identity = NULL;
+ do {
+ plist_array_next_item(build_identities, iter, &item);
+ if (!item) {
+ break;
+ }
+ unsigned int bi_board_id = (unsigned int)plist_dict_get_uint(item, "ApBoardID");
+ unsigned int bi_chip_id = (unsigned int)plist_dict_get_uint(item, "ApChipID");
+ if (bi_chip_id == chip_id && bi_board_id == board_id) {
+ build_identity = item;
+ break;
+ }
+ } while (item);
+ plist_mem_free(iter);
+ if (!build_identity) {
+ fprintf(stderr, "Error: The given disk image is not compatible with the current device.\n");
+ goto leave;
+ }
+ plist_t p_tc_path = plist_access_path(build_identity, 4, "Manifest", "LoadableTrustCache", "Info", "Path");
+ if (!p_tc_path) {
+ fprintf(stderr, "Error: Could not determine path for trust cache!\n");
+ goto leave;
+ }
+ plist_t p_dmg_path = plist_access_path(build_identity, 4, "Manifest", "PersonalizedDMG", "Info", "Path");
+ if (!p_dmg_path) {
+ fprintf(stderr, "Error: Could not determine path for disk image!\n");
+ goto leave;
+ }
+ char *tc_path = string_build_path(image_path, plist_get_string_ptr(p_tc_path, NULL), NULL);
+ unsigned char* trust_cache = NULL;
+ uint64_t trust_cache_size = 0;
+ if (!buffer_read_from_filename(tc_path, (char**)&trust_cache, &trust_cache_size)) {
+ fprintf(stderr, "Error: Trust cache does not exist at '%s'!\n", tc_path);
+ goto leave;
+ }
+ mount_options = plist_new_dict();
+ plist_dict_set_item(mount_options, "ImageTrustCache", plist_new_data((char*)trust_cache, trust_cache_size));
+ free(trust_cache);
+ char *dmg_path = string_build_path(image_path, plist_get_string_ptr(p_dmg_path, NULL), NULL);
+ free(image_path);
+ image_path = dmg_path;
+ f = fopen(image_path, "rb");
+ if (!f) {
+ fprintf(stderr, "Error opening image file '%s': %s\n", image_path, strerror(errno));
+ goto leave;
+ }
+
+ unsigned char buf[8192];
+ unsigned char sha384_digest[48];
+ sha384_context ctx;
+ sha384_init(&ctx);
+ fstat(fileno(f), &fst);
+ image_size = fst.st_size;
+ while (!feof(f)) {
+ ssize_t fr = fread(buf, 1, sizeof(buf), f);
+ if (fr <= 0) {
+ break;
+ }
+ sha384_update(&ctx, buf, fr);
+ }
+ rewind(f);
+ sha384_final(&ctx, sha384_digest);
+ unsigned char* manifest = NULL;
+ unsigned int manifest_size = 0;
+ /* check if the device already has a personalization manifest for this image */
+ if (mobile_image_mounter_query_personalization_manifest(mim, "DeveloperDiskImage", sha384_digest, sizeof(sha384_digest), &manifest, &manifest_size) == MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
+ printf("Using existing personalization manifest from device.\n");
+ } else {
+ /* we need to re-connect in this case */
+ mobile_image_mounter_free(mim);
+ mim = NULL;
+ if (mobile_image_mounter_start_service(device, &mim, TOOL_NAME) != MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
+ goto error_out;
+ }
+ printf("No personalization manifest, requesting from TSS...\n");
+ unsigned char* nonce = NULL;
+ unsigned int nonce_size = 0;
+
+ /* create new TSS request and fill parameters */
+ plist_t request = tss_request_new(NULL);
+ plist_t params = plist_new_dict();
+ tss_parameters_add_from_manifest(params, build_identity, 1);
+
+ /* copy all `Ap,*` items from identifiers */
+ plist_dict_iter di = NULL;
+ plist_dict_new_iter(identifiers, &di);
+ plist_t node = NULL;
+ do {
+ char* key = NULL;
+ plist_dict_next_item(identifiers, di, &key, &node);
+ if (node) {
+ if (!strncmp(key, "Ap,", 3)) {
+ plist_dict_set_item(request, key, plist_copy(node));
+ }
+ }
+ free(key);
+ } while (node);
+ plist_mem_free(di);
+
+ plist_dict_copy_uint(params, identifiers, "ApECID", "UniqueChipID");
+ plist_dict_set_item(params, "ApProductionMode", plist_new_bool(1));
+ plist_dict_set_item(params, "ApSecurityMode", plist_new_bool(1));
+ plist_dict_set_item(params, "ApSupportsImg4", plist_new_bool(1));
+
+ /* query nonce from image mounter service */
+ merr = mobile_image_mounter_query_nonce(mim, "DeveloperDiskImage", &nonce, &nonce_size);
+ if (merr == MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
+ plist_dict_set_item(params, "ApNonce", plist_new_data((char*)nonce, nonce_size));
+ } else {
+ fprintf(stderr, "ERROR: Failed to query nonce for developer disk image: %d\n", merr);
+ goto error_out;
+ }
+ mobile_image_mounter_free(mim);
+ mim = NULL;
+
+ plist_dict_set_item(params, "ApSepNonce", plist_new_data("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 20));
+ plist_dict_set_item(params, "UID_MODE", plist_new_bool(0));
+ tss_request_add_ap_tags(request, params, NULL);
+ tss_request_add_common_tags(request, params, NULL);
+ tss_request_add_ap_img4_tags(request, params);
+ plist_free(params);
+
+ /* request IM4M from TSS */
+ plist_t response = tss_request_send(request, NULL);
+ plist_free(request);
+
+ plist_t p_manifest = plist_dict_get_item(response, "ApImg4Ticket");
+ if (!PLIST_IS_DATA(p_manifest)) {
+ fprintf(stderr, "Failed to get Img4Ticket\n");
+ goto error_out;
+ }
+
+ uint64_t m4m_len = 0;
+ plist_get_data_val(p_manifest, (char**)&manifest, &m4m_len);
+ manifest_size = m4m_len;
+ plist_free(response);
+ printf("Done.\n");
+ }
+ sig = manifest;
+ sig_length = manifest_size;
+
+ imagetype = "Personalized";
}
char *targetname = NULL;
- if (asprintf(&targetname, "%s/%s", PKG_PATH, basename(image_path)) < 0) {
+ if (asprintf(&targetname, "%s/%s", PKG_PATH, "staging.dimage") < 0) {
fprintf(stderr, "Out of memory!?\n");
goto leave;
}
@@ -379,68 +588,91 @@ int main(int argc, char **argv)
goto leave;
}
- printf("Copying '%s' --> '%s'\n", image_path, targetname);
-
- char **strs = NULL;
- if (afc_get_file_info(afc, PKG_PATH, &strs) != AFC_E_SUCCESS) {
- if (afc_make_directory(afc, PKG_PATH) != AFC_E_SUCCESS) {
- fprintf(stderr, "WARNING: Could not create directory '%s' on device!\n", PKG_PATH);
- }
- }
- if (strs) {
- int i = 0;
- while (strs[i]) {
- free(strs[i]);
- i++;
- }
- free(strs);
+ if (!imagetype) {
+ imagetype = "Developer";
}
- uint64_t af = 0;
- if ((afc_file_open(afc, targetname, AFC_FOPEN_WRONLY, &af) !=
- AFC_E_SUCCESS) || !af) {
- fclose(f);
- fprintf(stderr, "afc_file_open on '%s' failed!\n", targetname);
- goto leave;
+ if (!mim) {
+ if (mobile_image_mounter_start_service(device, &mim, TOOL_NAME) != MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
+ goto error_out;
+ }
}
- char buf[8192];
- size_t amount = 0;
- do {
- amount = fread(buf, 1, sizeof(buf), f);
- if (amount > 0) {
- uint32_t written, total = 0;
- while (total < amount) {
- written = 0;
- if (afc_file_write(afc, af, buf, amount, &written) !=
- AFC_E_SUCCESS) {
- fprintf(stderr, "AFC Write error!\n");
- break;
+ switch(disk_image_upload_type) {
+ case DISK_IMAGE_UPLOAD_TYPE_UPLOAD_IMAGE:
+ printf("Uploading %s\n", image_path);
+ err = mobile_image_mounter_upload_image(mim, imagetype, image_size, sig, sig_length, mim_upload_cb, f);
+ break;
+ case DISK_IMAGE_UPLOAD_TYPE_AFC:
+ default:
+ printf("Uploading %s --> afc:///%s\n", image_path, targetname);
+ char **strs = NULL;
+ if (afc_get_file_info(afc, PKG_PATH, &strs) != AFC_E_SUCCESS) {
+ if (afc_make_directory(afc, PKG_PATH) != AFC_E_SUCCESS) {
+ fprintf(stderr, "WARNING: Could not create directory '%s' on device!\n", PKG_PATH);
+ }
+ }
+ if (strs) {
+ int i = 0;
+ while (strs[i]) {
+ free(strs[i]);
+ i++;
}
- total += written;
+ free(strs);
}
- if (total != amount) {
- fprintf(stderr, "Error: wrote only %d of %d\n", total,
- (unsigned int)amount);
- afc_file_close(afc, af);
+
+ uint64_t af = 0;
+ if ((afc_file_open(afc, targetname, AFC_FOPEN_WRONLY, &af) !=
+ AFC_E_SUCCESS) || !af) {
fclose(f);
+ fprintf(stderr, "afc_file_open on '%s' failed!\n", targetname);
goto leave;
}
- }
+
+ char buf[8192];
+ size_t amount = 0;
+ do {
+ amount = fread(buf, 1, sizeof(buf), f);
+ if (amount > 0) {
+ uint32_t written, total = 0;
+ while (total < amount) {
+ written = 0;
+ if (afc_file_write(afc, af, buf + total, amount - total, &written) !=
+ AFC_E_SUCCESS) {
+ fprintf(stderr, "AFC Write error!\n");
+ break;
+ }
+ total += written;
+ }
+ if (total != amount) {
+ fprintf(stderr, "Error: wrote only %d of %d\n", total,
+ (unsigned int)amount);
+ afc_file_close(afc, af);
+ fclose(f);
+ goto leave;
+ }
+ }
+ }
+ while (amount > 0);
+
+ afc_file_close(afc, af);
+ break;
}
- while (amount > 0);
- afc_file_close(afc, af);
fclose(f);
+ if (err != MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
+ if (err == MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED) {
+ printf("ERROR: Device is locked, can't mount. Unlock device and try again.\n");
+ } else {
+ printf("ERROR: Unknown error occurred, can't mount.\n");
+ }
+ goto error_out;
+ }
printf("done.\n");
printf("Mounting...\n");
- if (!imagetype) {
- imagetype = strdup("Developer");
- }
- err = mobile_image_mounter_mount_image(mim, mountname, sig, sig_length, imagetype, &result);
- free(imagetype);
+ err = mobile_image_mounter_mount_image_with_options(mim, mountname, sig, sig_length, imagetype, mount_options, &result);
if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
if (result) {
plist_t node = plist_dict_get_item(result, "Status");
@@ -453,20 +685,12 @@ int main(int argc, char **argv)
res = 0;
} else {
printf("unexpected status value:\n");
- if (xml_mode) {
- print_xml(result);
- } else {
- plist_dict_to_string(result);
- }
+ plist_write_to_stream(result, stdout, (xml_mode) ? PLIST_FORMAT_XML : PLIST_FORMAT_LIMD, 0);
}
free(status);
} else {
printf("unexpected result:\n");
- if (xml_mode) {
- print_xml(result);
- } else {
- plist_dict_to_string(result);
- }
+ plist_write_to_stream(result, stdout, (xml_mode) ? PLIST_FORMAT_XML : PLIST_FORMAT_LIMD, 0);
}
}
node = plist_dict_get_item(result, "Error");
@@ -478,31 +702,58 @@ int main(int argc, char **argv)
free(error);
} else {
printf("unexpected result:\n");
- if (xml_mode) {
- print_xml(result);
- } else {
- plist_dict_to_string(result);
- }
+ plist_write_to_stream(result, stdout, (xml_mode) ? PLIST_FORMAT_XML : PLIST_FORMAT_LIMD, 0);
}
-
- } else {
- if (xml_mode) {
- print_xml(result);
- } else {
- plist_dict_to_string(result);
+ node = plist_dict_get_item(result, "DetailedError");
+ if (node) {
+ printf("DetailedError: %s\n", plist_get_string_ptr(node, NULL));
}
+ } else {
+ plist_write_to_stream(result, stdout, (xml_mode) ? PLIST_FORMAT_XML : PLIST_FORMAT_LIMD, 0);
}
}
} else {
printf("Error: mount_image returned %d\n", err);
}
+ } else if (cmd == CMD_UNMOUNT) {
+ err = mobile_image_mounter_unmount_image(mim, argv[0]);
+ switch (err) {
+ case MOBILE_IMAGE_MOUNTER_E_SUCCESS:
+ printf("Success\n");
+ res = 0;
+ break;
+ case MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED:
+ printf("Error: '%s' is not mounted\n", argv[0]);
+ res = 1;
+ break;
+ case MOBILE_IMAGE_MOUNTER_E_NOT_SUPPORTED:
+ printf("Error: 'unmount' is not supported on this device\n");
+ res = 1;
+ break;
+ case MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED:
+ printf("Error: device is locked\n");
+ res = 1;
+ break;
+ default:
+ printf("Error: unmount returned %d\n", err);
+ break;
+ }
+ } else if (cmd == CMD_DEVMODESTATUS) {
+ err = mobile_image_mounter_query_developer_mode_status(mim, &result);
+ if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
+ res = 0;
+ plist_write_to_stream(result, stdout, (xml_mode) ? PLIST_FORMAT_XML : PLIST_FORMAT_LIMD, 0);
+ } else {
+ printf("Error: query_developer_mode_status returned %d\n", err);
+ }
}
if (result) {
plist_free(result);
}
+error_out:
/* perform hangup command */
mobile_image_mounter_hangup(mim);
/* free client */
@@ -518,7 +769,7 @@ leave:
idevice_free(device);
if (image_path)
- free(image_path);
+ free(image_path);
if (image_sig_path)
free(image_sig_path);
diff --git a/tools/ideviceinfo.c b/tools/ideviceinfo.c
index e05165b..fd45763 100644
--- a/tools/ideviceinfo.c
+++ b/tools/ideviceinfo.c
@@ -2,40 +2,59 @@
* ideviceinfo.c
* Simple utility to show information about an attached device
*
+ * Copyright (c) 2010-2019 Nikias Bassen, All Rights Reserved.
* Copyright (c) 2009 Martin Szulecki All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "ideviceinfo"
+
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
-#include <glib.h>
+#include <getopt.h>
+#ifndef WIN32
+#include <signal.h>
+#endif
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/lockdown.h>
+#include <plist/plist.h>
#define FORMAT_KEY_VALUE 1
#define FORMAT_XML 2
static const char *domains[] = {
"com.apple.disk_usage",
+ "com.apple.disk_usage.factory",
"com.apple.mobile.battery",
-/* FIXME: For some reason lockdownd segfaults on this, works sometimes though
+/* FIXME: For some reason lockdownd segfaults on this, works sometimes though
"com.apple.mobile.debug",. */
+ "com.apple.iqagent",
+ "com.apple.purplebuddy",
+ "com.apple.PurpleBuddy",
+ "com.apple.mobile.chaperone",
+ "com.apple.mobile.third_party_termination",
+ "com.apple.mobile.lockdownd",
+ "com.apple.mobile.lockdown_cache",
"com.apple.xcode.developerdomain",
"com.apple.international",
"com.apple.mobile.data_sync",
@@ -55,12 +74,12 @@ static const char *domains[] = {
"com.apple.iTunes",
"com.apple.mobile.iTunes.store",
"com.apple.mobile.iTunes",
+ "com.apple.fmip",
+ "com.apple.Accessibility",
NULL
};
-static int indent_level = 0;
-
-static int is_domain_known(char *domain)
+static int is_domain_known(const char *domain)
{
int i = 0;
while (domains[i] != NULL) {
@@ -71,244 +90,147 @@ static int is_domain_known(char *domain)
return 0;
}
-static void plist_node_to_string(plist_t node);
-
-static void plist_array_to_string(plist_t node)
-{
- /* iterate over items */
- int i, count;
- plist_t subnode = NULL;
-
- count = plist_array_get_size(node);
-
- for (i = 0; i < count; i++) {
- subnode = plist_array_get_item(node, i);
- printf("%*s", indent_level, "");
- printf("%d: ", i);
- plist_node_to_string(subnode);
- }
-}
-
-static void plist_dict_to_string(plist_t node)
-{
- /* iterate over key/value pairs */
- plist_dict_iter it = NULL;
-
- char* key = NULL;
- plist_t subnode = NULL;
- plist_dict_new_iter(node, &it);
- plist_dict_next_item(node, it, &key, &subnode);
- while (subnode)
- {
- printf("%*s", indent_level, "");
- printf("%s", key);
- if (plist_get_node_type(subnode) == PLIST_ARRAY)
- printf("[%d]: ", plist_array_get_size(subnode));
- else
- printf(": ");
- free(key);
- key = NULL;
- plist_node_to_string(subnode);
- plist_dict_next_item(node, it, &key, &subnode);
- }
- free(it);
-}
-
-static void plist_node_to_string(plist_t node)
-{
- char *s = NULL;
- char *data = NULL;
- double d;
- uint8_t b;
- uint64_t u = 0;
- GTimeVal tv = { 0, 0 };
-
- plist_type t;
-
- if (!node)
- return;
-
- t = plist_get_node_type(node);
-
- switch (t) {
- case PLIST_BOOLEAN:
- plist_get_bool_val(node, &b);
- printf("%s\n", (b ? "true" : "false"));
- break;
-
- case PLIST_UINT:
- plist_get_uint_val(node, &u);
- printf("%llu\n", (long long)u);
- break;
-
- case PLIST_REAL:
- plist_get_real_val(node, &d);
- printf("%f\n", d);
- break;
-
- case PLIST_STRING:
- plist_get_string_val(node, &s);
- printf("%s\n", s);
- free(s);
- break;
-
- case PLIST_KEY:
- plist_get_key_val(node, &s);
- printf("%s: ", s);
- free(s);
- break;
-
- case PLIST_DATA:
- plist_get_data_val(node, &data, &u);
- s = g_base64_encode((guchar *)data, u);
- free(data);
- printf("%s\n", s);
- g_free(s);
- break;
-
- case PLIST_DATE:
- plist_get_date_val(node, (int32_t*)&tv.tv_sec, (int32_t*)&tv.tv_usec);
- s = g_time_val_to_iso8601(&tv);
- printf("%s\n", s);
- free(s);
- break;
-
- case PLIST_ARRAY:
- printf("\n");
- indent_level++;
- plist_array_to_string(node);
- indent_level--;
- break;
-
- case PLIST_DICT:
- printf("\n");
- indent_level++;
- plist_dict_to_string(node);
- indent_level--;
- break;
-
- default:
- break;
- }
-}
-
-static void print_usage(int argc, char **argv)
+static void print_usage(int argc, char **argv, int is_error)
{
int i = 0;
- char *name = NULL;
-
- name = strrchr(argv[0], '/');
- printf("Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0]));
- printf("Show information about a connected iPhone/iPod Touch.\n\n");
- printf(" -d, --debug\t\tenable communication debugging\n");
- printf(" -s, --simple\t\tuse a simple connection to avoid auto-pairing with the device\n");
- printf(" -u, --uuid UUID\ttarget specific device by its 40-digit device UUID\n");
- printf(" -q, --domain NAME\tset domain of query to NAME. Default: None\n");
- printf(" -k, --key NAME\tonly query key specified by NAME. Default: All keys.\n");
- printf(" -x, --xml\t\toutput information as xml plist instead of key/value pairs\n");
- printf(" -h, --help\t\tprints usage information\n");
- printf("\n");
- printf(" Known domains are:\n\n");
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Show information about a connected device.\n"
+ "\n"
+ "OPTIONS:\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ " -n, --network connect to network device\n"
+ " -s, --simple use simple connection to avoid auto-pairing with device\n"
+ " -q, --domain NAME set domain of query to NAME. Default: None\n" \
+ " -k, --key NAME only query key specified by NAME. Default: All keys.\n" \
+ " -x, --xml output information in XML property list format\n" \
+ " -h, --help prints usage information\n" \
+ " -d, --debug enable communication debugging\n" \
+ " -v, --version prints version information\n" \
+ "\n"
+ );
+ fprintf(is_error ? stderr : stdout, "Known domains are:\n\n");
while (domains[i] != NULL) {
- printf(" %s\n", domains[i++]);
+ fprintf(is_error ? stderr : stdout, " %s\n", domains[i++]);
}
- printf("\n");
+ fprintf(is_error ? stderr : stdout,
+ "\n" \
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
}
int main(int argc, char *argv[])
{
lockdownd_client_t client = NULL;
- idevice_t phone = NULL;
+ lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR;
+ idevice_t device = NULL;
idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR;
- int i;
int simple = 0;
int format = FORMAT_KEY_VALUE;
- char uuid[41];
- char *domain = NULL;
- char *key = NULL;
+ const char* udid = NULL;
+ int use_network = 0;
+ const char *domain = NULL;
+ const char *key = NULL;
char *xml_doc = NULL;
uint32_t xml_length;
plist_t node = NULL;
- plist_type node_type;
- uuid[0] = 0;
- /* parse cmdline args */
- for (i = 1; i < argc; i++) {
- if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) {
+ int c = 0;
+ const struct option longopts[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+ { "network", no_argument, NULL, 'n' },
+ { "domain", required_argument, NULL, 'q' },
+ { "key", required_argument, NULL, 'k' },
+ { "simple", no_argument, NULL, 's' },
+ { "xml", no_argument, NULL, 'x' },
+ { "version", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0}
+ };
+
+#ifndef WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ while ((c = getopt_long(argc, argv, "dhu:nq:k:sxv", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
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], "-q") || !strcmp(argv[i], "--domain")) {
- i++;
- if (!argv[i] || (strlen(argv[i]) < 4)) {
- print_usage(argc, argv);
- return 0;
+ break;
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
}
- if (!is_domain_known(argv[i])) {
- fprintf(stderr, "WARNING: Sending query with unknown domain \"%s\".\n", argv[i]);
+ udid = optarg;
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 'q':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: 'domain' must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
}
- domain = strdup(argv[i]);
- continue;
- }
- else if (!strcmp(argv[i], "-k") || !strcmp(argv[i], "--key")) {
- i++;
- if (!argv[i] || (strlen(argv[i]) <= 1)) {
- print_usage(argc, argv);
- return 0;
+ domain = optarg;
+ break;
+ case 'k':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: 'key' must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
}
- key = strdup(argv[i]);
- continue;
- }
- else if (!strcmp(argv[i], "-x") || !strcmp(argv[i], "--xml")) {
+ key = optarg;
+ break;
+ case 'x':
format = FORMAT_XML;
- continue;
- }
- else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--simple")) {
+ break;
+ case 's':
simple = 1;
- continue;
- }
- else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
- print_usage(argc, argv);
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
return 0;
- }
- else {
- print_usage(argc, argv);
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
return 0;
+ default:
+ print_usage(argc, argv, 1);
+ return 2;
}
}
- 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;
+ argc -= optind;
+ argv += optind;
+
+ ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX);
+ if (ret != IDEVICE_E_SUCCESS) {
+ if (udid) {
+ fprintf(stderr, "ERROR: Device %s not found!\n", udid);
+ } else {
+ fprintf(stderr, "ERROR: No device found!\n");
}
+ return -1;
}
- if (LOCKDOWN_E_SUCCESS != (simple ?
- lockdownd_client_new(phone, &client, "ideviceinfo"):
- lockdownd_client_new_with_handshake(phone, &client, "ideviceinfo"))) {
- idevice_free(phone);
+ if (LOCKDOWN_E_SUCCESS != (ldret = simple ?
+ lockdownd_client_new(device, &client, TOOL_NAME):
+ lockdownd_client_new_with_handshake(device, &client, TOOL_NAME))) {
+ fprintf(stderr, "ERROR: Could not connect to lockdownd: %s (%d)\n", lockdownd_strerror(ldret), ldret);
+ idevice_free(device);
return -1;
}
+ if (domain && !is_domain_known(domain)) {
+ fprintf(stderr, "WARNING: Sending query with unknown domain \"%s\".\n", domain);
+ }
+
/* run query and output information */
if(lockdownd_get_value(client, domain, key, &node) == LOCKDOWN_E_SUCCESS) {
if (node) {
@@ -319,16 +241,11 @@ int main(int argc, char *argv[])
free(xml_doc);
break;
case FORMAT_KEY_VALUE:
- node_type = plist_get_node_type(node);
- if (node_type == PLIST_DICT) {
- plist_dict_to_string(node);
- } else if (node_type == PLIST_ARRAY) {
- plist_array_to_string(node);
- break;
- }
+ plist_write_to_stream(node, stdout, PLIST_FORMAT_LIMD, 0);
+ break;
default:
if (key != NULL)
- plist_node_to_string(node);
+ plist_write_to_stream(node, stdout, PLIST_FORMAT_LIMD, 0);
break;
}
plist_free(node);
@@ -336,10 +253,8 @@ int main(int argc, char *argv[])
}
}
- if (domain != NULL)
- free(domain);
lockdownd_client_free(client);
- idevice_free(phone);
+ idevice_free(device);
return 0;
}
diff --git a/tools/idevicename.c b/tools/idevicename.c
new file mode 100644
index 0000000..69b76f6
--- /dev/null
+++ b/tools/idevicename.c
@@ -0,0 +1,159 @@
+/*
+ * idevicename.c
+ * Simple utility to get or set the device name
+ *
+ * Copyright (c) 2014 Nikias Bassen, All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "idevicename"
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <getopt.h>
+#ifndef WIN32
+#include <signal.h>
+#endif
+
+#include <libimobiledevice/libimobiledevice.h>
+#include <libimobiledevice/lockdown.h>
+
+static void print_usage(int argc, char** argv, int is_error)
+{
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] [NAME]\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Display the device name or set it to NAME if specified.\n"
+ "\n"
+ "OPTIONS:\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ " -n, --network connect to network device\n"
+ " -d, --debug enable communication debugging\n"
+ " -h, --help print usage information\n"
+ " -v, --version print version information\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
+}
+
+int main(int argc, char** argv)
+{
+ int c = 0;
+ const struct option longopts[] = {
+ { "udid", required_argument, NULL, 'u' },
+ { "network", no_argument, NULL, 'n' },
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0}
+ };
+ int res = -1;
+ const char* udid = NULL;
+ int use_network = 0;
+
+#ifndef WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ while ((c = getopt_long(argc, argv, "du:hnv", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID must not be empty!\n");
+ print_usage(argc, argv, 1);
+ exit(2);
+ }
+ udid = optarg;
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
+ return 0;
+ case 'd':
+ idevice_set_debug_level(1);
+ break;
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
+ return 0;
+ default:
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 1) {
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+
+ idevice_t device = NULL;
+ if (idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX) != IDEVICE_E_SUCCESS) {
+ if (udid) {
+ fprintf(stderr, "ERROR: No device found with udid %s.\n", udid);
+ } else {
+ fprintf(stderr, "ERROR: No device found.\n");
+ }
+ return -1;
+ }
+
+ lockdownd_client_t lockdown = NULL;
+ lockdownd_error_t lerr = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME);
+ if (lerr != LOCKDOWN_E_SUCCESS) {
+ idevice_free(device);
+ fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", lerr);
+ return -1;
+ }
+
+ if (argc == 0) {
+ // getting device name
+ char* name = NULL;
+ lerr = lockdownd_get_device_name(lockdown, &name);
+ if (name) {
+ printf("%s\n", name);
+ free(name);
+ res = 0;
+ } else {
+ fprintf(stderr, "ERROR: Could not get device name, lockdown error %d\n", lerr);
+ }
+ } else {
+ // setting device name
+ lerr = lockdownd_set_value(lockdown, NULL, "DeviceName", plist_new_string(argv[0]));
+ if (lerr == LOCKDOWN_E_SUCCESS) {
+ printf("device name set to '%s'\n", argv[0]);
+ res = 0;
+ } else {
+ fprintf(stderr, "ERROR: Could not set device name, lockdown error %d\n", lerr);
+ }
+ }
+
+ lockdownd_client_free(lockdown);
+ idevice_free(device);
+
+ return res;
+}
diff --git a/tools/idevicenotificationproxy.c b/tools/idevicenotificationproxy.c
new file mode 100644
index 0000000..d1e25c1
--- /dev/null
+++ b/tools/idevicenotificationproxy.c
@@ -0,0 +1,293 @@
+/*
+ * idevicenotificationproxy.c
+ * Simple client for the notification_proxy service
+ *
+ * Copyright (c) 2009-2015 Martin Szulecki All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "idevicenotificationproxy"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <errno.h>
+#include <signal.h>
+
+#ifdef WIN32
+#include <windows.h>
+#define sleep(x) Sleep(x*1000)
+#else
+#include <unistd.h>
+#endif
+
+#include <libimobiledevice/libimobiledevice.h>
+#include <libimobiledevice/lockdown.h>
+#include <libimobiledevice/notification_proxy.h>
+
+enum cmd_mode {
+ CMD_NONE = 0,
+ CMD_OBSERVE,
+ CMD_POST
+};
+
+static int quit_flag = 0;
+
+/**
+ * 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, int is_error)
+{
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] COMMAND\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Post or observe notifications on a device.\n"
+ "\n"
+ "Where COMMAND is one of:\n"
+ " post ID [...] post notification IDs to device and exit\n"
+ " observe ID [...] observe notification IDs in foreground until CTRL+C\n"
+ " or signal is received\n"
+ "\n"
+ "The following OPTIONS are accepted:\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ " -n, --network connect to network device\n"
+ " -d, --debug enable communication debugging\n"
+ " -h, --help prints usage information\n"
+ " -v, --version prints version information\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
+}
+
+static void notify_cb(const char *notification, void *user_data)
+{
+ printf("> %s\n", notification);
+}
+
+int main(int argc, char *argv[])
+{
+ lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
+ lockdownd_service_descriptor_t service = NULL;
+ lockdownd_client_t client = NULL;
+ idevice_t device = NULL;
+ np_client_t gnp = NULL;
+
+ int result = -1;
+ int i = 0;
+ const char* udid = NULL;
+ int use_network = 0;
+ int cmd = CMD_NONE;
+ char* cmd_arg = NULL;
+
+ int count = 0;
+ char **nspec = NULL;
+ char **nspectmp = NULL;
+
+ int c = 0;
+ const struct option longopts[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+ { "network", no_argument, NULL, 'n' },
+ { "version", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0}
+ };
+
+ signal(SIGINT, clean_exit);
+ signal(SIGTERM, clean_exit);
+#ifndef WIN32
+ signal(SIGQUIT, clean_exit);
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ /* parse cmdline args */
+ while ((c = getopt_long(argc, argv, "dhu:nv", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ idevice_set_debug_level(1);
+ break;
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID argument must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ udid = optarg;
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
+ return 0;
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
+ return 0;
+ default:
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!argv[i]) {
+ fprintf(stderr, "ERROR: Missing command\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+
+ if (!strcmp(argv[i], "post")) {
+ cmd = CMD_POST;
+ } else if (!strcmp(argv[i], "observe")) {
+ cmd = CMD_OBSERVE;
+ }
+
+ if (cmd == CMD_POST || cmd == CMD_OBSERVE) {
+ i++;
+ if (!argv[i]) {
+ fprintf(stderr, "ERROR: Please supply a valid notification identifier.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+
+ count = 0;
+ nspec = malloc(sizeof(char*) * (count+1));
+
+ while(1) {
+ if (argv[i] && (strlen(argv[i]) >= 2) && (strncmp(argv[i], "-", 1) != 0)) {
+ nspectmp = realloc(nspec, sizeof(char*) * (count+1));
+ nspectmp[count] = strdup(argv[i]);
+ nspec = nspectmp;
+ count = count+1;
+ i++;
+ } else {
+ i--;
+ break;
+ }
+ }
+
+ nspectmp = realloc(nspec, sizeof(char*) * (count+1));
+ nspectmp[count] = NULL;
+ nspec = nspectmp;
+ }
+
+ /* verify options */
+ if (cmd == CMD_NONE) {
+ fprintf(stderr, "ERROR: Unsupported command '%s'\n", argv[0]);
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+
+ if (IDEVICE_E_SUCCESS != idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX)) {
+ if (udid) {
+ printf("No device found with udid %s.\n", udid);
+ } else {
+ printf("No device found.\n");
+ }
+ goto cleanup;
+ }
+
+ if (LOCKDOWN_E_SUCCESS != (ret = lockdownd_client_new_with_handshake(device, &client, TOOL_NAME))) {
+ fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", ret);
+ goto cleanup;
+ }
+
+ ret = lockdownd_start_service(client, NP_SERVICE_NAME, &service);
+
+ lockdownd_client_free(client);
+
+ if ((ret == LOCKDOWN_E_SUCCESS) && (service->port > 0)) {
+ if (np_client_new(device, service, &gnp) != NP_E_SUCCESS) {
+ printf("Could not connect to notification_proxy!\n");
+ result = -1;
+ } else {
+ np_set_notify_callback(gnp, notify_cb, NULL);
+
+ switch (cmd) {
+ case CMD_POST:
+ i = 0;
+ while(nspec[i] != NULL && i < (count+1)) {
+ printf("< posting \"%s\"\n", nspec[i]);
+ np_post_notification(gnp, nspec[i]);
+ i++;
+ }
+ break;
+ case CMD_OBSERVE:
+ default:
+ i = 0;
+ while(nspec[i] != NULL && i < (count+1)) {
+ printf("! observing \"%s\"\n", nspec[i]);
+ np_observe_notification(gnp, nspec[i]);
+ i++;
+ }
+
+ /* just sleep and wait for notifications */
+ while (!quit_flag) {
+ sleep(1);
+ }
+
+ break;
+ }
+
+ result = EXIT_SUCCESS;
+
+ if (gnp) {
+ np_client_free(gnp);
+ gnp = NULL;
+ }
+ }
+ } else {
+ printf("ERROR: Could not start service %s: %s\n", NP_SERVICE_NAME, lockdownd_strerror(ret));
+ }
+
+ if (service) {
+ lockdownd_service_descriptor_free(service);
+ service = NULL;
+ }
+
+cleanup:
+ if (nspec) {
+ i = 0;
+ while(nspec[i] != NULL && i < (count+1)) {
+ free(nspec[i]);
+ i++;
+ }
+ free(nspec);
+ }
+
+ if (cmd_arg) {
+ free(cmd_arg);
+ }
+
+ if (device)
+ idevice_free(device);
+
+ return result;
+}
diff --git a/tools/idevicepair.c b/tools/idevicepair.c
index b9676b9..94d3f04 100644
--- a/tools/idevicepair.c
+++ b/tools/idevicepair.c
@@ -1,114 +1,314 @@
/*
* idevicepair.c
- * Simple utility to pair/unpair an iDevice
+ * Manage pairings with devices and this host
*
- * Copyright (c) 2010 Nikias Bassen All Rights Reserved.
+ * Copyright (c) 2010-2021 Nikias Bassen, All Rights Reserved.
+ * Copyright (c) 2014 Martin Szulecki, All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "idevicepair"
+
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
-#include "userpref.h"
+#include <ctype.h>
+#include <unistd.h>
+#ifdef WIN32
+#include <windows.h>
+#include <conio.h>
+#else
+#include <termios.h>
+#include <signal.h>
+#endif
+
+#include "common/userpref.h"
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/lockdown.h>
+#include <plist/plist.h>
+
+static char *udid = NULL;
-static char *uuid = NULL;
+#ifdef HAVE_WIRELESS_PAIRING
-static void print_usage(int argc, char **argv)
+#ifdef WIN32
+#define BS_CC '\b'
+#define my_getch getch
+#else
+#define BS_CC 0x7f
+static int my_getch(void)
{
- char *name = NULL;
-
- name = strrchr(argv[0], '/');
- printf("\n%s - Manage pairings with iPhone/iPod Touch/iPad devices and this host.\n\n", (name ? name + 1: argv[0]));
- printf("Usage: %s [OPTIONS] COMMAND\n\n", (name ? name + 1: argv[0]));
- printf(" Where COMMAND is one of:\n");
- printf(" hostid print the host id of this computer\n");
- printf(" pair pair device with this computer\n");
- printf(" validate validate if device is paired with this computer\n");
- printf(" unpair unpair device with this computer\n");
- printf(" list list devices paired with this computer\n\n");
- printf(" The following OPTIONS are accepted:\n");
- printf(" -d, --debug enable communication debugging\n");
- printf(" -u, --uuid UUID target specific device by its 40-digit device UUID\n");
- printf(" -h, --help prints usage information\n");
- printf("\n");
+ 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 parse_opts(int argc, char **argv)
+static int get_hidden_input(char *buf, int maxlen)
{
- static struct option longopts[] = {
- {"help", 0, NULL, 'h'},
- {"uuid", 1, NULL, 'u'},
- {"debug", 0, NULL, 'd'},
- {NULL, 0, NULL, 0}
- };
+ int pwlen = 0;
int c;
- while (1) {
- c = getopt_long(argc, argv, "hu:d", longopts, (int*)0);
- if (c == -1) {
+ 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;
+ return pwlen;
+}
- switch (c) {
- case 'h':
- print_usage(argc, argv);
- exit(EXIT_SUCCESS);
- case 'u':
- if (strlen(optarg) != 40) {
- printf("%s: invalid UUID specified (length != 40)\n", argv[0]);
- print_usage(argc, argv);
- exit(2);
- }
- uuid = strdup(optarg);
+static void pairing_cb(lockdownd_cu_pairing_cb_type_t cb_type, void *user_data, void* data_ptr, unsigned int* data_size)
+{
+ if (cb_type == LOCKDOWN_CU_PAIRING_PIN_REQUESTED) {
+ printf("Enter PIN: ");
+ fflush(stdout);
+
+ *data_size = get_hidden_input((char*)data_ptr, *data_size);
+
+ printf("\n");
+ } else if (cb_type == LOCKDOWN_CU_PAIRING_DEVICE_INFO) {
+ printf("Device info:\n");
+ plist_write_to_stream((plist_t)data_ptr, stdout, PLIST_FORMAT_LIMD, PLIST_OPT_INDENT | PLIST_OPT_INDENT_BY(2));
+ } else if (cb_type == LOCKDOWN_CU_PAIRING_ERROR) {
+ printf("ERROR: %s\n", (data_ptr) ? (char*)data_ptr : "(unknown)");
+ }
+}
+
+#endif /* HAVE_WIRELESS_PAIRING */
+
+static void print_error_message(lockdownd_error_t err)
+{
+ switch (err) {
+ case LOCKDOWN_E_PASSWORD_PROTECTED:
+ printf("ERROR: Could not validate with device %s because a passcode is set. Please enter the passcode on the device and retry.\n", udid);
break;
- case 'd':
- idevice_set_debug_level(1);
+ case LOCKDOWN_E_INVALID_CONF:
+ case LOCKDOWN_E_INVALID_HOST_ID:
+ printf("ERROR: Device %s is not paired with this host\n", udid);
+ break;
+ case LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING:
+ printf("ERROR: Please accept the trust dialog on the screen of device %s, then attempt to pair again.\n", udid);
+ break;
+ case LOCKDOWN_E_USER_DENIED_PAIRING:
+ printf("ERROR: Device %s said that the user denied the trust dialog.\n", udid);
+ break;
+ case LOCKDOWN_E_PAIRING_FAILED:
+ printf("ERROR: Pairing with device %s failed.\n", udid);
+ break;
+ case LOCKDOWN_E_GET_PROHIBITED:
+ case LOCKDOWN_E_PAIRING_PROHIBITED_OVER_THIS_CONNECTION:
+ printf("ERROR: Pairing is not possible over this connection.\n");
+#ifdef HAVE_WIRELESS_PAIRING
+ printf("To perform a wireless pairing use the -w command line switch. See usage or man page for details.\n");
+#endif
break;
default:
- print_usage(argc, argv);
- exit(EXIT_SUCCESS);
- }
+ printf("ERROR: Device %s returned unhandled error code %d\n", udid, err);
+ break;
}
}
+static void print_usage(int argc, char **argv, int is_error)
+{
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] COMMAND\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Manage host pairings with devices and usbmuxd.\n"
+ "\n"
+ "Where COMMAND is one of:\n"
+ " systembuid print the system buid of the usbmuxd host\n"
+ " hostid print the host id for target device\n"
+ " pair pair device with this host\n"
+ " validate validate if device is paired with this host\n"
+ " unpair unpair device with this host\n"
+ " list list devices paired with this host\n"
+ "\n"
+ "The following OPTIONS are accepted:\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ );
+#ifdef HAVE_WIRELESS_PAIRING
+ fprintf(is_error ? stderr : stdout,
+ " -w, --wireless perform wireless pairing (see NOTE)\n"
+ " -n, --network connect to network device (see NOTE)\n"
+ );
+#endif
+ fprintf(is_error ? stderr : stdout,
+ " -d, --debug enable communication debugging\n"
+ " -h, --help prints usage information\n"
+ " -v, --version prints version information\n"
+ );
+#ifdef HAVE_WIRELESS_PAIRING
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "NOTE: Pairing over network (wireless pairing) is only supported by Apple TV\n"
+ "devices. To perform a wireless pairing, you need to use the -w command line\n"
+ "switch. Make sure to put the device into pairing mode first by opening\n"
+ "Settings > Remotes and Devices > Remote App and Devices.\n"
+ );
+#endif
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
+}
+
int main(int argc, char **argv)
{
+ int c = 0;
+ static struct option longopts[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+#ifdef HAVE_WIRELESS_PAIRING
+ { "wireless", no_argument, NULL, 'w' },
+ { "network", no_argument, NULL, 'n' },
+ { "hostinfo", required_argument, NULL, 1 },
+#endif
+ { "debug", no_argument, NULL, 'd' },
+ { "version", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0}
+ };
+#ifdef HAVE_WIRELESS_PAIRING
+#define SHORT_OPTIONS "hu:wndv"
+#else
+#define SHORT_OPTIONS "hu:dv"
+#endif
lockdownd_client_t client = NULL;
- idevice_t phone = NULL;
+ idevice_t device = NULL;
idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR;
lockdownd_error_t lerr;
int result;
char *type = NULL;
+ int use_network = 0;
+ int wireless_pairing = 0;
+#ifdef HAVE_WIRELESS_PAIRING
+ plist_t host_info_plist = NULL;
+#endif
char *cmd;
typedef enum {
- OP_NONE = 0, OP_PAIR, OP_VALIDATE, OP_UNPAIR, OP_LIST, OP_HOSTID
+ OP_NONE = 0, OP_PAIR, OP_VALIDATE, OP_UNPAIR, OP_LIST, OP_HOSTID, OP_SYSTEMBUID
} op_t;
op_t op = OP_NONE;
- parse_opts(argc, argv);
+ while ((c = getopt_long(argc, argv, SHORT_OPTIONS, longopts, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ print_usage(argc, argv, 0);
+ exit(EXIT_SUCCESS);
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID must not be empty!\n");
+ print_usage(argc, argv, 1);
+ result = EXIT_FAILURE;
+ goto leave;
+ }
+ free(udid);
+ udid = strdup(optarg);
+ break;
+#ifdef HAVE_WIRELESS_PAIRING
+ case 'w':
+ wireless_pairing = 1;
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 1:
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: --hostinfo argument must not be empty!\n");
+ result = EXIT_FAILURE;
+ goto leave;
+ }
+ if (*optarg == '@') {
+ plist_read_from_file(optarg+1, &host_info_plist, NULL);
+ if (!host_info_plist) {
+ fprintf(stderr, "ERROR: Could not read from file '%s'\n", optarg+1);
+ result = EXIT_FAILURE;
+ goto leave;
+ }
+ }
+#ifdef HAVE_PLIST_JSON
+ else if (*optarg == '{') {
+ if (plist_from_json(optarg, strlen(optarg), &host_info_plist) != PLIST_ERR_SUCCESS) {
+ fprintf(stderr, "ERROR: --hostinfo argument not valid. Make sure it is a JSON dictionary.\n");
+ result = EXIT_FAILURE;
+ goto leave;
+ }
+ }
+#endif
+ else {
+ fprintf(stderr, "ERROR: --hostinfo argument not valid. To specify a path prefix with '@'\n");
+ result = EXIT_FAILURE;
+ goto leave;
+ }
+ break;
+#endif
+ case 'd':
+ idevice_set_debug_level(1);
+ break;
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
+ result = EXIT_SUCCESS;
+ goto leave;
+ default:
+ print_usage(argc, argv, 1);
+ result = EXIT_FAILURE;
+ goto leave;
+ }
+ }
+
+#ifndef WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
if ((argc - optind) < 1) {
- printf("ERROR: You need to specify a COMMAND!\n");
- print_usage(argc, argv);
- exit(EXIT_FAILURE);
+ fprintf(stderr, "ERROR: You need to specify a COMMAND!\n");
+ print_usage(argc, argv, 1);
+ result = EXIT_FAILURE;
+ goto leave;
+ }
+
+ if (wireless_pairing && use_network) {
+ fprintf(stderr, "ERROR: You cannot use -w and -n together.\n");
+ print_usage(argc, argv, 1);
+ result = EXIT_FAILURE;
+ goto leave;
}
cmd = (argv+optind)[0];
@@ -123,60 +323,91 @@ int main(int argc, char **argv)
op = OP_LIST;
} else if (!strcmp(cmd, "hostid")) {
op = OP_HOSTID;
+ } else if (!strcmp(cmd, "systembuid")) {
+ op = OP_SYSTEMBUID;
} else {
- printf("ERROR: Invalid command '%s' specified\n", cmd);
- print_usage(argc, argv);
- exit(EXIT_FAILURE);
+ fprintf(stderr, "ERROR: Invalid command '%s' specified\n", cmd);
+ print_usage(argc, argv, 1);
+ result = EXIT_FAILURE;
+ goto leave;
}
- if (op == OP_HOSTID) {
- char *hostid = NULL;
- userpref_get_host_id(&hostid);
+ if (wireless_pairing) {
+ if (op == OP_VALIDATE || op == OP_UNPAIR) {
+ fprintf(stderr, "ERROR: Command '%s' is not supported with -w\n", cmd);
+ print_usage(argc, argv, 1);
+ result = EXIT_FAILURE;
+ goto leave;
+ }
+ use_network = 1;
+ }
- printf("%s\n", hostid);
+ if (op == OP_SYSTEMBUID) {
+ char *systembuid = NULL;
+ userpref_read_system_buid(&systembuid);
- if (hostid)
- free(hostid);
+ printf("%s\n", systembuid);
- return EXIT_SUCCESS;
+ free(systembuid);
+
+ result = EXIT_SUCCESS;
+ goto leave;
}
if (op == OP_LIST) {
unsigned int i;
- char **uuids = NULL;
+ char **udids = NULL;
unsigned int count = 0;
- userpref_get_paired_uuids(&uuids, &count);
+ userpref_get_paired_udids(&udids, &count);
for (i = 0; i < count; i++) {
- printf("%s\n", uuids[i]);
+ printf("%s\n", udids[i]);
+ free(udids[i]);
}
- if (uuids)
- g_strfreev(uuids);
- if (uuid)
- free(uuid);
- return EXIT_SUCCESS;
+ free(udids);
+ result = EXIT_SUCCESS;
+ goto leave;
}
- if (uuid) {
- ret = idevice_new(&phone, uuid);
- free(uuid);
- uuid = NULL;
- if (ret != IDEVICE_E_SUCCESS) {
- printf("No device found with uuid %s, is it plugged in?\n", uuid);
- return EXIT_FAILURE;
+ 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");
}
- } else {
- ret = idevice_new(&phone, NULL);
+ result = EXIT_FAILURE;
+ goto leave;
+ }
+ if (!udid) {
+ ret = idevice_get_udid(device, &udid);
if (ret != IDEVICE_E_SUCCESS) {
- printf("No device found, is it plugged in?\n");
- return EXIT_FAILURE;
+ printf("ERROR: Could not get device udid, error code %d\n", ret);
+ result = EXIT_FAILURE;
+ goto leave;
}
}
- lerr = lockdownd_client_new(phone, &client, "idevicepair");
+ if (op == OP_HOSTID) {
+ plist_t pair_record = NULL;
+ char *hostid = NULL;
+
+ userpref_read_pair_record(udid, &pair_record);
+ pair_record_get_host_id(pair_record, &hostid);
+
+ printf("%s\n", hostid);
+
+ free(hostid);
+ plist_free(pair_record);
+
+ result = EXIT_SUCCESS;
+ goto leave;
+ }
+
+ lerr = lockdownd_client_new(device, &client, TOOL_NAME);
if (lerr != LOCKDOWN_E_SUCCESS) {
- idevice_free(phone);
- printf("ERROR: lockdownd_client_new failed with error code %d\n", lerr);
- return EXIT_FAILURE;
+ printf("ERROR: Could not connect to lockdownd, error code %d\n", lerr);
+ result = EXIT_FAILURE;
+ goto leave;
}
result = EXIT_SUCCESS;
@@ -187,76 +418,62 @@ int main(int argc, char **argv)
result = EXIT_FAILURE;
goto leave;
} else {
- if (strcmp("com.apple.mobile.lockdown", type)) {
+ if (strcmp("com.apple.mobile.lockdown", type) != 0) {
printf("WARNING: QueryType request returned '%s'\n", type);
}
- if (type) {
- free(type);
- }
- }
-
- ret = idevice_get_uuid(phone, &uuid);
- if (ret != IDEVICE_E_SUCCESS) {
- printf("ERROR: Could not get device uuid, error code %d\n", ret);
- result = EXIT_FAILURE;
- goto leave;
+ free(type);
}
switch(op) {
default:
case OP_PAIR:
- lerr = lockdownd_pair(client, NULL);
+#ifdef HAVE_WIRELESS_PAIRING
+ if (wireless_pairing) {
+ lerr = lockdownd_cu_pairing_create(client, pairing_cb, NULL, host_info_plist, NULL);
+ if (lerr == LOCKDOWN_E_SUCCESS) {
+ lerr = lockdownd_pair_cu(client);
+ }
+ } else
+#endif
+ {
+ lerr = lockdownd_pair(client, NULL);
+ }
if (lerr == LOCKDOWN_E_SUCCESS) {
- printf("SUCCESS: Paired with device %s\n", uuid);
+ printf("SUCCESS: Paired with device %s\n", udid);
} else {
result = EXIT_FAILURE;
- if (lerr == LOCKDOWN_E_PASSWORD_PROTECTED) {
- printf("ERROR: Could not pair with the device because a passcode is set. Please enter the passcode on the device and retry.\n");
- } else {
- printf("ERROR: Pairing with device %s failed with unhandled error code %d\n", uuid, lerr);
- }
+ print_error_message(lerr);
}
break;
case OP_VALIDATE:
- lerr = lockdownd_validate_pair(client, NULL);
+ lockdownd_client_free(client);
+ client = NULL;
+ lerr = lockdownd_client_new_with_handshake(device, &client, TOOL_NAME);
if (lerr == LOCKDOWN_E_SUCCESS) {
- printf("SUCCESS: Validated pairing with device %s\n", uuid);
+ printf("SUCCESS: Validated pairing with device %s\n", udid);
} else {
result = EXIT_FAILURE;
- if (lerr == LOCKDOWN_E_PASSWORD_PROTECTED) {
- printf("ERROR: Could not validate with the device because a passcode is set. Please enter the passcode on the device and retry.\n");
- } else if (lerr == LOCKDOWN_E_INVALID_HOST_ID) {
- printf("ERROR: Device %s is not paired with this host\n", uuid);
- } else {
- printf("ERROR: Pairing failed with unhandled error code %d\n", lerr);
- }
+ print_error_message(lerr);
}
break;
case OP_UNPAIR:
lerr = lockdownd_unpair(client, NULL);
if (lerr == LOCKDOWN_E_SUCCESS) {
- /* also remove local device public key */
- userpref_remove_device_public_key(uuid);
- printf("SUCCESS: Unpaired with device %s\n", uuid);
+ printf("SUCCESS: Unpaired with device %s\n", udid);
} else {
result = EXIT_FAILURE;
- if (lerr == LOCKDOWN_E_INVALID_HOST_ID) {
- printf("ERROR: Device %s is not paired with this host\n", uuid);
- } else {
- printf("ERROR: Unpairing with device %s failed with unhandled error code %d\n", uuid, lerr);
- }
+ print_error_message(lerr);
}
break;
}
leave:
lockdownd_client_free(client);
- idevice_free(phone);
- if (uuid) {
- free(uuid);
- }
+ idevice_free(device);
+ free(udid);
+
return result;
}
diff --git a/tools/ideviceprovision.c b/tools/ideviceprovision.c
new file mode 100644
index 0000000..4080a28
--- /dev/null
+++ b/tools/ideviceprovision.c
@@ -0,0 +1,689 @@
+/*
+ * ideviceprovision.c
+ * Simple utility to install, get, or remove provisioning profiles
+ * to/from idevices
+ *
+ * Copyright (c) 2012-2016 Nikias Bassen, All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "ideviceprovision"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <sys/stat.h>
+#include <errno.h>
+#ifndef WIN32
+#include <signal.h>
+#endif
+
+#ifdef WIN32
+#include <windows.h>
+#else
+#include <arpa/inet.h>
+#endif
+
+#include <libimobiledevice/libimobiledevice.h>
+#include <libimobiledevice/lockdown.h>
+#include <libimobiledevice/misagent.h>
+#include <plist/plist.h>
+
+static void print_usage(int argc, char **argv, int is_error)
+{
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] COMMAND\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Manage provisioning profiles on a device.\n"
+ "\n"
+ "Where COMMAND is one of:\n"
+ " install FILE Installs the provisioning profile specified by FILE.\n"
+ " A valid .mobileprovision file is expected.\n"
+ " list Get a list of all provisioning profiles on the device.\n"
+ " copy PATH Retrieves all provisioning profiles from the device and\n"
+ " stores them into the existing directory specified by PATH.\n"
+ " The files will be stored as UUID.mobileprovision\n"
+ " copy UUID PATH Retrieves the provisioning profile identified by UUID\n"
+ " from the device and stores it into the existing directory\n"
+ " specified by PATH. The file will be stored as UUID.mobileprovision.\n"
+ " remove UUID Removes the provisioning profile identified by UUID.\n"
+ " remove-all Removes all installed provisioning profiles.\n"
+ " dump FILE Prints detailed information about the provisioning profile\n"
+ " specified by FILE.\n"
+ "\n"
+ "The following OPTIONS are accepted:\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ " -n, --network connect to network device\n"
+ " -x, --xml print XML output when using the 'dump' command\n"
+ " -d, --debug enable communication debugging\n"
+ " -h, --help prints usage information\n"
+ " -v, --version prints version information\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
+}
+
+enum {
+ OP_INSTALL,
+ OP_LIST,
+ OP_COPY,
+ OP_REMOVE,
+ OP_DUMP,
+ NUM_OPS
+};
+
+#define ASN1_SEQUENCE 0x30
+#define ASN1_CONTAINER 0xA0
+#define ASN1_OBJECT_IDENTIFIER 0x06
+#define ASN1_OCTET_STRING 0x04
+
+static void asn1_next_item(unsigned char** p)
+{
+ char bsize = *(*p+1);
+ if (bsize & 0x80) {
+ *p += 2 + (bsize & 0xF);
+ } else {
+ *p += 3;
+ }
+}
+
+static size_t asn1_item_get_size(const unsigned char* p)
+{
+ size_t res = 0;
+ char bsize = *(p+1);
+ if (bsize & 0x80) {
+ uint16_t ws = 0;
+ uint32_t ds = 0;
+ switch (bsize & 0xF) {
+ case 2:
+ ws = *(uint16_t*)(p+2);
+ res = ntohs(ws);
+ break;
+ case 3:
+ ds = *(uint32_t*)(p+2);
+ res = ntohl(ds) >> 8;
+ break;
+ case 4:
+ ds = *(uint32_t*)(p+2);
+ res = ntohl(ds);
+ break;
+ default:
+ fprintf(stderr, "ERROR: Invalid or unimplemented byte size %d\n", bsize & 0xF);
+ break;
+ }
+ } else {
+ res = (int)bsize;
+ }
+ return res;
+}
+
+static void asn1_skip_item(unsigned char** p)
+{
+ size_t sz = asn1_item_get_size(*p);
+ *p += 2;
+ *p += sz;
+}
+
+static plist_t profile_get_embedded_plist(plist_t profile)
+{
+ if (plist_get_node_type(profile) != PLIST_DATA) {
+ fprintf(stderr, "%s: unexpected plist node type for profile (PLIST_DATA expected)\n", __func__);
+ return NULL;
+ }
+ char* bbuf = NULL;
+ uint64_t blen = 0;
+ plist_get_data_val(profile, &bbuf, &blen);
+ if (!bbuf) {
+ fprintf(stderr, "%s: could not get data value from plist node\n", __func__);
+ return NULL;
+ }
+
+ unsigned char* pp = (unsigned char*)bbuf;
+
+ if (*pp != ASN1_SEQUENCE) {
+ free(bbuf);
+ fprintf(stderr, "%s: unexpected profile data (0)\n", __func__);
+ return NULL;
+ }
+ size_t slen = asn1_item_get_size(pp);
+ char bsize = *(pp+1);
+ if (bsize & 0x80) {
+ slen += 2 + (bsize & 0xF);
+ } else {
+ slen += 3;
+ }
+ if (slen != blen) {
+ free(bbuf);
+ fprintf(stderr, "%s: unexpected profile data (1)\n", __func__);
+ return NULL;
+ }
+ asn1_next_item(&pp);
+
+ if (*pp != ASN1_OBJECT_IDENTIFIER) {
+ free(bbuf);
+ fprintf(stderr, "%s: unexpected profile data (2)\n", __func__);
+ return NULL;
+ }
+ asn1_skip_item(&pp);
+
+ if (*pp != ASN1_CONTAINER) {
+ free(bbuf);
+ fprintf(stderr, "%s: unexpected profile data (3)\n", __func__);
+ return NULL;
+ }
+ asn1_next_item(&pp);
+
+ if (*pp != ASN1_SEQUENCE) {
+ free(bbuf);
+ fprintf(stderr, "%s: unexpected profile data (4)\n", __func__);
+ return NULL;
+ }
+ asn1_next_item(&pp);
+
+ int k = 0;
+ // go to the 3rd element (skip 2)
+ while (k < 2) {
+ asn1_skip_item(&pp);
+ k++;
+ }
+ if (*pp != ASN1_SEQUENCE) {
+ free(bbuf);
+ fprintf(stderr, "%s: unexpected profile data (5)\n", __func__);
+ return NULL;
+ }
+ asn1_next_item(&pp);
+
+ if (*pp != ASN1_OBJECT_IDENTIFIER) {
+ free(bbuf);
+ fprintf(stderr, "%s: unexpected profile data (6)\n", __func__);
+ return NULL;
+ }
+ asn1_skip_item(&pp);
+
+ if (*pp != ASN1_CONTAINER) {
+ free(bbuf);
+ fprintf(stderr, "%s: unexpected profile data (7)\n", __func__);
+ return NULL;
+ }
+ asn1_next_item(&pp);
+
+ if (*pp != ASN1_OCTET_STRING) {
+ free(bbuf);
+ fprintf(stderr, "%s: unexpected profile data (8)\n", __func__);
+ return NULL;
+ }
+ slen = asn1_item_get_size(pp);
+ asn1_next_item(&pp);
+
+ plist_t pl = NULL;
+ plist_from_xml((char*)pp, slen, &pl);
+ free(bbuf);
+
+ return pl;
+}
+
+static int profile_read_from_file(const char* path, unsigned char **profile_data, unsigned int *profile_size)
+{
+ FILE* f = fopen(path, "rb");
+ if (!f) {
+ fprintf(stderr, "Could not open file '%s'\n", path);
+ return -1;
+ }
+ fseek(f, 0, SEEK_END);
+ long int size = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ if (size >= 0x1000000) {
+ fprintf(stderr, "The file '%s' is too large for processing.\n", path);
+ fclose(f);
+ return -1;
+ }
+
+ unsigned char* buf = malloc(size);
+ if (!buf) {
+ fprintf(stderr, "Could not allocate memory...\n");
+ fclose(f);
+ return -1;
+ }
+
+ long int cur = 0;
+ while (cur < size) {
+ ssize_t r = fread(buf+cur, 1, 512, f);
+ if (r <= 0) {
+ break;
+ }
+ cur += r;
+ }
+ fclose(f);
+
+ if (cur != size) {
+ free(buf);
+ fprintf(stderr, "Could not read in file '%s' (size %ld read %ld)\n", path, size, cur);
+ return -1;
+ }
+
+ *profile_data = buf;
+ *profile_size = (unsigned int)size;
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ lockdownd_client_t client = NULL;
+ lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR;
+ lockdownd_service_descriptor_t service = NULL;
+ idevice_t device = NULL;
+ idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR;
+ int res = 0;
+ int i;
+ int op = -1;
+ int output_xml = 0;
+ const char* udid = NULL;
+ const char* param = NULL;
+ const char* param2 = NULL;
+ int use_network = 0;
+ int c = 0;
+ const struct option longopts[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+ { "network", no_argument, NULL, 'n' },
+ { "version", no_argument, NULL, 'v' },
+ { "xml", no_argument, NULL, 'x' },
+ { NULL, 0, NULL, 0}
+ };
+
+#ifndef WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
+ /* parse cmdline args */
+ while ((c = getopt_long(argc, argv, "dhu:nvx", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ idevice_set_debug_level(1);
+ break;
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID argument must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ udid = optarg;
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
+ return 0;
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
+ return 0;
+ case 'x':
+ output_xml = 1;
+ break;
+ default:
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!argv[0]) {
+ fprintf(stderr, "ERROR: Missing command.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+
+ i = 0;
+ if (!strcmp(argv[i], "install")) {
+ op = OP_INSTALL;
+ i++;
+ if (!argv[i] || !*argv[i]) {
+ fprintf(stderr, "Missing argument for 'install' command.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+ param = argv[i];
+ }
+ else if (!strcmp(argv[i], "list")) {
+ op = OP_LIST;
+ }
+ else if (!strcmp(argv[i], "copy")) {
+ op = OP_COPY;
+ i++;
+ if (!argv[i] || !*argv[i]) {
+ fprintf(stderr, "Missing argument for 'copy' command.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+ param = argv[i];
+ i++;
+ if (argv[i] && (strlen(argv[i]) > 0)) {
+ param2 = argv[i];
+ }
+ }
+ else if (!strcmp(argv[i], "remove")) {
+ op = OP_REMOVE;
+ i++;
+ if (!argv[i] || !*argv[i]) {
+ fprintf(stderr, "Missing argument for 'remove' command.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+ param = argv[i];
+ }
+ else if (!strcmp(argv[i], "remove-all")) {
+ op = OP_REMOVE;
+ }
+ else if (!strcmp(argv[i], "dump")) {
+ op = OP_DUMP;
+ i++;
+ if (!argv[i] || !*argv[i]) {
+ fprintf(stderr, "Missing argument for 'remove' command.\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+ param = argv[i];
+ }
+ if ((op == -1) || (op >= NUM_OPS)) {
+ fprintf(stderr, "ERROR: Unsupported command '%s'\n", argv[i]);
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+
+ if (op == OP_DUMP) {
+ unsigned char* profile_data = NULL;
+ unsigned int profile_size = 0;
+ if (profile_read_from_file(param, &profile_data, &profile_size) != 0) {
+ return -1;
+ }
+ plist_t pdata = plist_new_data((char*)profile_data, profile_size);
+ plist_t pl = profile_get_embedded_plist(pdata);
+ plist_free(pdata);
+ free(profile_data);
+
+ if (pl) {
+ if (output_xml) {
+ char* xml = NULL;
+ uint32_t xlen = 0;
+ plist_to_xml(pl, &xml, &xlen);
+ if (xml) {
+ printf("%s\n", xml);
+ free(xml);
+ }
+ } else {
+ if (pl && (plist_get_node_type(pl) == PLIST_DICT)) {
+ plist_write_to_stream(pl, stdout, PLIST_FORMAT_LIMD, 0);
+ } else {
+ fprintf(stderr, "ERROR: unexpected node type in profile plist (not PLIST_DICT)\n");
+ res = -1;
+ }
+ }
+ } else {
+ fprintf(stderr, "ERROR: could not extract embedded plist from profile!\n");
+ }
+ plist_free(pl);
+
+ return res;
+ }
+
+ if (op == OP_COPY) {
+ struct stat st;
+ const char *checkdir = (param2) ? param2 : param;
+ if ((stat(checkdir, &st) < 0) || !S_ISDIR(st.st_mode)) {
+ fprintf(stderr, "ERROR: %s does not exist or is not a directory!\n", checkdir);
+ 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 (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &client, TOOL_NAME))) {
+ fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", ldret);
+ idevice_free(device);
+ return -1;
+ }
+
+ plist_t pver = NULL;
+ char *pver_s = NULL;
+ lockdownd_get_value(client, NULL, "ProductVersion", &pver);
+ if (pver && plist_get_node_type(pver) == PLIST_STRING) {
+ plist_get_string_val(pver, &pver_s);
+ }
+ plist_free(pver);
+ int product_version_major = 0;
+ int product_version_minor = 0;
+ int product_version_patch = 0;
+ if (pver_s) {
+ sscanf(pver_s, "%d.%d.%d", &product_version_major, &product_version_minor, &product_version_patch);
+ free(pver_s);
+ }
+ if (product_version_major == 0) {
+ fprintf(stderr, "ERROR: Could not determine the device's ProductVersion\n");
+ lockdownd_client_free(client);
+ idevice_free(device);
+ return -1;
+ }
+ int product_version = ((product_version_major & 0xFF) << 16) | ((product_version_minor & 0xFF) << 8) | (product_version_patch & 0xFF);
+
+ lockdownd_error_t lerr = lockdownd_start_service(client, MISAGENT_SERVICE_NAME, &service);
+ if (lerr != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "Could not start service %s: %s\n", MISAGENT_SERVICE_NAME, lockdownd_strerror(lerr));
+ lockdownd_client_free(client);
+ idevice_free(device);
+ return -1;
+ }
+ lockdownd_client_free(client);
+ client = NULL;
+
+ misagent_client_t mis = NULL;
+ if (misagent_client_new(device, service, &mis) != MISAGENT_E_SUCCESS) {
+ fprintf(stderr, "Could not connect to %s on device\n", MISAGENT_SERVICE_NAME);
+ if (service)
+ lockdownd_service_descriptor_free(service);
+ lockdownd_client_free(client);
+ idevice_free(device);
+ return -1;
+ }
+
+ if (service)
+ lockdownd_service_descriptor_free(service);
+
+ switch (op) {
+ case OP_INSTALL:
+ {
+ unsigned char* profile_data = NULL;
+ unsigned int profile_size = 0;
+ if (profile_read_from_file(param, &profile_data, &profile_size) != 0) {
+ break;
+ }
+
+ uint64_t psize = profile_size;
+ plist_t pdata = plist_new_data((const char*)profile_data, psize);
+ free(profile_data);
+
+ if (misagent_install(mis, pdata) == MISAGENT_E_SUCCESS) {
+ printf("Profile '%s' installed successfully.\n", param);
+ } else {
+ int sc = misagent_get_status_code(mis);
+ fprintf(stderr, "Could not install profile '%s', status code: 0x%x\n", param, sc);
+ }
+ }
+ break;
+ case OP_LIST:
+ case OP_COPY:
+ {
+ plist_t profiles = NULL;
+ misagent_error_t merr;
+ if (product_version < 0x090300) {
+ merr = misagent_copy(mis, &profiles);
+ } else {
+ merr = misagent_copy_all(mis, &profiles);
+ }
+ if (merr == MISAGENT_E_SUCCESS) {
+ int found_match = 0;
+ uint32_t num_profiles = plist_array_get_size(profiles);
+ if (op == OP_LIST || !param2) {
+ printf("Device has %d provisioning %s installed:\n", num_profiles, (num_profiles == 1) ? "profile" : "profiles");
+ }
+ uint32_t j;
+ for (j = 0; !found_match && j < num_profiles; j++) {
+ char* p_name = NULL;
+ char* p_uuid = NULL;
+ plist_t profile = plist_array_get_item(profiles, j);
+ plist_t pl = profile_get_embedded_plist(profile);
+ if (pl && (plist_get_node_type(pl) == PLIST_DICT)) {
+ plist_t node;
+ node = plist_dict_get_item(pl, "Name");
+ if (node && (plist_get_node_type(node) == PLIST_STRING)) {
+ plist_get_string_val(node, &p_name);
+ }
+ node = plist_dict_get_item(pl, "UUID");
+ if (node && (plist_get_node_type(node) == PLIST_STRING)) {
+ plist_get_string_val(node, &p_uuid);
+ }
+ }
+ if (param2) {
+ if (p_uuid && !strcmp(p_uuid, param)) {
+ found_match = 1;
+ } else {
+ free(p_uuid);
+ free(p_name);
+ continue;
+ }
+ }
+ printf("%s - %s\n", (p_uuid) ? p_uuid : "(unknown id)", (p_name) ? p_name : "(no name)");
+ if (op == OP_COPY) {
+ char pfname[512];
+ if (p_uuid) {
+ sprintf(pfname, "%s/%s.mobileprovision", (param2) ? param2 : param, p_uuid);
+ } else {
+ sprintf(pfname, "%s/profile%d.mobileprovision", (param2) ? param2 : param, j);
+ }
+ FILE* f = fopen(pfname, "wb");
+ if (f) {
+ char* dt = NULL;
+ uint64_t ds = 0;
+ plist_get_data_val(profile, &dt, &ds);
+ fwrite(dt, 1, ds, f);
+ fclose(f);
+ printf(" => %s\n", pfname);
+ } else {
+ fprintf(stderr, "Could not open '%s' for writing: %s\n", pfname, strerror(errno));
+ }
+ }
+ free(p_uuid);
+ free(p_name);
+ }
+ if (param2 && !found_match) {
+ fprintf(stderr, "Profile '%s' was not found on the device.\n", param);
+ res = -1;
+ }
+ } else {
+ int sc = misagent_get_status_code(mis);
+ fprintf(stderr, "Could not get installed profiles from device, status code: 0x%x\n", sc);
+ res = -1;
+ }
+ plist_free(profiles);
+ }
+ break;
+ case OP_REMOVE:
+ if (param) {
+ /* remove specified provisioning profile */
+ if (misagent_remove(mis, param) == MISAGENT_E_SUCCESS) {
+ printf("Profile '%s' removed.\n", param);
+ } else {
+ int sc = misagent_get_status_code(mis);
+ fprintf(stderr, "Could not remove profile '%s', status code 0x%x\n", param, sc);
+ }
+ } else {
+ /* remove all provisioning profiles */
+ plist_t profiles = NULL;
+ misagent_error_t merr;
+ if (product_version < 0x090300) {
+ merr = misagent_copy(mis, &profiles);
+ } else {
+ merr = misagent_copy_all(mis, &profiles);
+ }
+ if (merr == MISAGENT_E_SUCCESS) {
+ uint32_t j;
+ uint32_t num_removed = 0;
+ for (j = 0; j < plist_array_get_size(profiles); j++) {
+ char* p_name = NULL;
+ char* p_uuid = NULL;
+ plist_t profile = plist_array_get_item(profiles, j);
+ plist_t pl = profile_get_embedded_plist(profile);
+ if (pl && (plist_get_node_type(pl) == PLIST_DICT)) {
+ plist_t node;
+ node = plist_dict_get_item(pl, "Name");
+ if (node && (plist_get_node_type(node) == PLIST_STRING)) {
+ plist_get_string_val(node, &p_name);
+ }
+ node = plist_dict_get_item(pl, "UUID");
+ if (node && (plist_get_node_type(node) == PLIST_STRING)) {
+ plist_get_string_val(node, &p_uuid);
+ }
+ }
+ if (p_uuid) {
+ if (misagent_remove(mis, p_uuid) == MISAGENT_E_SUCCESS) {
+ printf("OK profile removed: %s - %s\n", p_uuid, (p_name) ? p_name : "(no name)");
+ num_removed++;
+ } else {
+ int sc = misagent_get_status_code(mis);
+ printf("FAIL profile not removed: %s - %s (status code 0x%x)\n", p_uuid, (p_name) ? p_name : "(no name)", sc);
+ }
+ }
+ free(p_name);
+ free(p_uuid);
+ }
+ printf("%d profiles removed.\n", num_removed);
+ } else {
+ int sc = misagent_get_status_code(mis);
+ fprintf(stderr, "Could not get installed profiles from device, status code: 0x%x\n", sc);
+ res = -1;
+ }
+ plist_free(profiles);
+ }
+ break;
+ default:
+ break;
+ }
+
+ misagent_client_free(mis);
+
+ idevice_free(device);
+
+ return res;
+}
+
diff --git a/tools/idevicescreenshot.c b/tools/idevicescreenshot.c
index 8567f77..0e694c7 100644
--- a/tools/idevicescreenshot.c
+++ b/tools/idevicescreenshot.c
@@ -1,113 +1,227 @@
-/**
- * idevicescreenshot -- Gets a screenshot from a connected iPhone/iPod Touch
+/*
+ * idevicescreenshot.c
+ * Gets a screenshot from a device
*
* Copyright (C) 2010 Nikias Bassen <nikias@gmx.li>
*
- * Licensed under the GNU General Public License Version 2
+ * 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 program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
+ * 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 General Public License for more profile.
+ * 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 General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
- * USA
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "idevicescreenshot"
+
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
+#include <getopt.h>
#include <errno.h>
#include <time.h>
+#include <unistd.h>
+#ifndef WIN32
+#include <signal.h>
+#endif
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/lockdown.h>
#include <libimobiledevice/screenshotr.h>
-void print_usage(int argc, char **argv);
+static void get_image_filename(char *imgdata, char **filename)
+{
+ // If the provided filename already has an extension, use it as is.
+ if (*filename) {
+ char *last_dot = strrchr(*filename, '.');
+ if (last_dot && !strchr(last_dot, '/')) {
+ return;
+ }
+ }
+
+ // Find the appropriate file extension for the filename.
+ const char *fileext = NULL;
+ if (memcmp(imgdata, "\x89PNG", 4) == 0) {
+ fileext = ".png";
+ } else if (memcmp(imgdata, "MM\x00*", 4) == 0) {
+ fileext = ".tiff";
+ } else {
+ printf("WARNING: screenshot data has unexpected image format.\n");
+ fileext = ".dat";
+ }
+
+ // If a filename without an extension is provided, append the extension.
+ // Otherwise, generate a filename based on the current time.
+ char *basename = NULL;
+ if (*filename) {
+ basename = (char*)malloc(strlen(*filename) + 1);
+ strcpy(basename, *filename);
+ free(*filename);
+ *filename = NULL;
+ } else {
+ time_t now = time(NULL);
+ basename = (char*)malloc(32);
+ strftime(basename, 31, "screenshot-%Y-%m-%d-%H-%M-%S", gmtime(&now));
+ }
+
+ // Ensure the filename is unique on disk.
+ char *unique_filename = (char*)malloc(strlen(basename) + strlen(fileext) + 7);
+ sprintf(unique_filename, "%s%s", basename, fileext);
+ int i;
+ for (i = 2; i < (1 << 16); i++) {
+ if (access(unique_filename, F_OK) == -1) {
+ *filename = unique_filename;
+ break;
+ }
+ sprintf(unique_filename, "%s-%d%s", basename, i, fileext);
+ }
+ if (!*filename) {
+ free(unique_filename);
+ }
+ free(basename);
+}
+
+static void print_usage(int argc, char **argv, int is_error)
+{
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] [FILE]\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Gets a screenshot from a connected device.\n"
+ "\n"
+ "The image is in PNG format for iOS 9+ and otherwise in TIFF format.\n"
+ "The screenshot is saved as an image with the given FILE name.\n"
+ "If FILE has no extension, FILE will be a prefix of the saved filename.\n"
+ "If FILE is not specified, \"screenshot-DATE\", will be used as a prefix\n"
+ "of the filename, e.g.:\n"
+ " ./screenshot-2013-12-31-23-59-59.tiff\n"
+ "\n"
+ "NOTE: A mounted developer disk image is required on the device, otherwise\n"
+ "the screenshotr service is not available.\n"
+ "\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ " -n, --network connect to network device\n"
+ " -d, --debug enable communication debugging\n"
+ " -h, --help prints usage information\n"
+ " -v, --version prints version information\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
+}
int main(int argc, char **argv)
{
idevice_t device = NULL;
lockdownd_client_t lckd = NULL;
+ lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR;
screenshotr_client_t shotr = NULL;
- uint16_t port = 0;
+ lockdownd_service_descriptor_t service = NULL;
int result = -1;
- int i;
- char *uuid = NULL;
+ const char *udid = NULL;
+ int use_network = 0;
+ char *filename = NULL;
+ int c = 0;
+ const struct option longopts[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+ { "network", no_argument, NULL, 'n' },
+ { "version", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0}
+ };
+#ifndef WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
/* parse cmdline args */
- for (i = 1; i < argc; i++) {
- if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) {
+
+ /* parse cmdline arguments */
+ while ((c = getopt_long(argc, argv, "dhu:nv", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
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;
+ break;
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID argument must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
}
- uuid = strdup(argv[i]);
- continue;
- }
- else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
- print_usage(argc, argv);
+ udid = optarg;
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
return 0;
- }
- else {
- print_usage(argc, argv);
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
return 0;
+ default:
+ print_usage(argc, argv, 1);
+ return 2;
}
}
+ argc -= optind;
+ argv += optind;
- if (IDEVICE_E_SUCCESS != idevice_new(&device, uuid)) {
- printf("No device found, is it plugged in?\n");
- if (uuid) {
- free(uuid);
+ if (argv[0]) {
+ filename = strdup(argv[0]);
+ }
+
+ if (IDEVICE_E_SUCCESS != idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX)) {
+ if (udid) {
+ printf("No device found with udid %s.\n", udid);
+ } else {
+ printf("No device found.\n");
}
return -1;
}
- if (uuid) {
- free(uuid);
- }
- if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(device, &lckd, NULL)) {
+ if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &lckd, TOOL_NAME))) {
idevice_free(device);
- printf("Exiting.\n");
+ printf("ERROR: Could not connect to lockdownd, error code %d\n", ldret);
return -1;
}
- lockdownd_start_service(lckd, "com.apple.mobile.screenshotr", &port);
+ lockdownd_error_t lerr = lockdownd_start_service(lckd, SCREENSHOTR_SERVICE_NAME, &service);
lockdownd_client_free(lckd);
- if (port > 0) {
- if (screenshotr_client_new(device, port, &shotr) != SCREENSHOTR_E_SUCCESS) {
+ if (lerr == LOCKDOWN_E_SUCCESS) {
+ if (screenshotr_client_new(device, service, &shotr) != SCREENSHOTR_E_SUCCESS) {
printf("Could not connect to screenshotr!\n");
} else {
char *imgdata = NULL;
- char filename[36];
uint64_t imgsize = 0;
- time_t now = time(NULL);
- strftime(filename, 36, "screenshot-%Y-%m-%d-%H-%M-%S.tiff", gmtime(&now));
if (screenshotr_take_screenshot(shotr, &imgdata, &imgsize) == SCREENSHOTR_E_SUCCESS) {
- FILE *f = fopen(filename, "w");
- if (f) {
- if (fwrite(imgdata, 1, (size_t)imgsize, f) == (size_t)imgsize) {
- printf("Screenshot saved to %s\n", filename);
- result = 0;
+ get_image_filename(imgdata, &filename);
+ if (!filename) {
+ printf("FATAL: Could not find a unique filename!\n");
+ } else {
+ FILE *f = fopen(filename, "wb");
+ if (f) {
+ if (fwrite(imgdata, 1, (size_t)imgsize, f) == (size_t)imgsize) {
+ printf("Screenshot saved to %s\n", filename);
+ result = 0;
+ } else {
+ printf("Could not save screenshot to file %s!\n", filename);
+ }
+ fclose(f);
} else {
- printf("Could not save screenshot to file %s!\n", filename);
+ printf("Could not open %s for writing: %s\n", filename, strerror(errno));
}
- fclose(f);
- } else {
- printf("Could not open %s for writing: %s\n", filename, strerror(errno));
}
} else {
printf("Could not get screenshot!\n");
@@ -115,25 +229,14 @@ int main(int argc, char **argv)
screenshotr_client_free(shotr);
}
} else {
- printf("Could not start screenshotr service! Remember that you have to mount the Developer disk image on your device if you want to use the screenshotr service.\n");
+ printf("Could not start screenshotr service: %s\nRemember that you have to mount the Developer disk image on your device if you want to use the screenshotr service.\n", lockdownd_strerror(lerr));
}
+
+ if (service)
+ lockdownd_service_descriptor_free(service);
+
idevice_free(device);
-
- return result;
-}
+ free(filename);
-void print_usage(int argc, char **argv)
-{
- char *name = NULL;
-
- name = strrchr(argv[0], '/');
- printf("Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0]));
- printf("Gets a screenshot from the connected iPhone/iPod Touch.\n");
- printf("The screenshot is saved as a TIFF image in the current directory.\n");
- printf("NOTE: A mounted developer disk image is required on the device, otherwise\n");
- printf("the screenshotr service is not available.\n\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");
+ return result;
}
diff --git a/tools/idevicesetlocation.c b/tools/idevicesetlocation.c
new file mode 100644
index 0000000..69fbaf5
--- /dev/null
+++ b/tools/idevicesetlocation.c
@@ -0,0 +1,192 @@
+/*
+ * idevicesetlocation.c
+ * Simulate location on iOS device with mounted developer disk image
+ *
+ * Copyright (c) 2016-2020 Nikias Bassen, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "idevicesetlocation"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+
+#include <libimobiledevice/libimobiledevice.h>
+#include <libimobiledevice/lockdown.h>
+#include <libimobiledevice/service.h>
+
+#include <endianness.h>
+
+#define DT_SIMULATELOCATION_SERVICE "com.apple.dt.simulatelocation"
+
+enum {
+ SET_LOCATION = 0,
+ RESET_LOCATION = 1
+};
+
+static void print_usage(int argc, char **argv, int is_error)
+{
+ char *bname = strrchr(argv[0], '/');
+ bname = (bname) ? bname + 1 : argv[0];
+
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] -- <LAT> <LONG>\n", bname);
+ fprintf(is_error ? stderr : stdout, " %s [OPTIONS] reset\n", bname);
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "OPTIONS:\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ " -n, --network connect to network device\n"
+ " -d, --debug enable communication debugging\n"
+ " -h, --help prints usage information\n"
+ " -v, --version prints version information\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
+}
+
+int main(int argc, char **argv)
+{
+ int c = 0;
+ const struct option longopts[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+ { "debug", no_argument, NULL, 'd' },
+ { "network", no_argument, NULL, 'n' },
+ { "version", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0}
+ };
+ uint32_t mode = 0;
+ const char *udid = NULL;
+ int use_network = 0;
+
+ while ((c = getopt_long(argc, argv, "dhu:nv", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ idevice_set_debug_level(1);
+ break;
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ udid = optarg;
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
+ return 0;
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
+ return 0;
+ default:
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if ((argc > 2) || (argc < 1)) {
+ print_usage(argc+optind, argv-optind, 1);
+ return -1;
+ }
+
+ if (argc == 2) {
+ mode = SET_LOCATION;
+ } else if (argc == 1) {
+ if (strcmp(argv[0], "reset") == 0) {
+ mode = RESET_LOCATION;
+ } else {
+ print_usage(argc+optind, argv-optind, 1);
+ return -1;
+ }
+ }
+
+ idevice_t device = NULL;
+
+ if (idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX) != IDEVICE_E_SUCCESS) {
+ if (udid) {
+ printf("ERROR: Device %s not found!\n", udid);
+ } else {
+ printf("ERROR: No device found!\n");
+ }
+ return -1;
+ }
+
+ lockdownd_client_t lockdown;
+ lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME);
+
+ lockdownd_service_descriptor_t svc = NULL;
+ lockdownd_error_t lerr = lockdownd_start_service(lockdown, DT_SIMULATELOCATION_SERVICE, &svc);
+ if (lerr != LOCKDOWN_E_SUCCESS) {
+ lockdownd_client_free(lockdown);
+ idevice_free(device);
+ printf("ERROR: Could not start the simulatelocation service: %s\nMake sure a developer disk image is mounted!\n", lockdownd_strerror(lerr));
+ return -1;
+ }
+ lockdownd_client_free(lockdown);
+
+ service_client_t service = NULL;
+
+ service_error_t serr = service_client_new(device, svc, &service);
+
+ lockdownd_service_descriptor_free(svc);
+
+ if (serr != SERVICE_E_SUCCESS) {
+ lockdownd_client_free(lockdown);
+ idevice_free(device);
+ printf("ERROR: Could not connect to simulatelocation service (%d)\n", serr);
+ return -1;
+ }
+
+ uint32_t l;
+ uint32_t s = 0;
+
+ l = htobe32(mode);
+ service_send(service, (const char*)&l, 4, &s);
+ if (mode == SET_LOCATION) {
+ int len = 4 + strlen(argv[0]) + 4 + strlen(argv[1]);
+ char *buf = malloc(len);
+ uint32_t latlen;
+ latlen = strlen(argv[0]);
+ l = htobe32(latlen);
+ memcpy(buf, &l, 4);
+ memcpy(buf+4, argv[0], latlen);
+ uint32_t longlen = strlen(argv[1]);
+ l = htobe32(longlen);
+ memcpy(buf+4+latlen, &l, 4);
+ memcpy(buf+4+latlen+4, argv[1], longlen);
+
+ s = 0;
+ service_send(service, buf, len, &s);
+ }
+
+ idevice_free(device);
+
+ return 0;
+}
diff --git a/tools/idevicesyslog.c b/tools/idevicesyslog.c
index f2b3a2f..a0e641d 100644
--- a/tools/idevicesyslog.c
+++ b/tools/idevicesyslog.c
@@ -2,169 +2,783 @@
* idevicesyslog.c
* Relay the syslog of a device to stdout
*
+ * Copyright (c) 2010-2020 Nikias Bassen, All Rights Reserved.
* Copyright (c) 2009 Martin Szulecki All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
- *
+ *
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define TOOL_NAME "idevicesyslog"
+
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
-#include <glib.h>
+#include <unistd.h>
+#include <getopt.h>
+
+#ifdef WIN32
+#include <windows.h>
+#define sleep(x) Sleep(x*1000)
+#endif
#include <libimobiledevice/libimobiledevice.h>
-#include <libimobiledevice/lockdown.h>
+#include <libimobiledevice/syslog_relay.h>
+#include <libimobiledevice-glue/termcolors.h>
static int quit_flag = 0;
+static int exit_on_disconnect = 0;
+static int show_device_name = 0;
+
+static char* udid = NULL;
+static char** proc_filters = NULL;
+static int num_proc_filters = 0;
+static int proc_filter_excluding = 0;
+
+static int* pid_filters = NULL;
+static int num_pid_filters = 0;
+
+static char** msg_filters = NULL;
+static int num_msg_filters = 0;
+
+static char** trigger_filters = NULL;
+static int num_trigger_filters = 0;
+static char** untrigger_filters = NULL;
+static int num_untrigger_filters = 0;
+static int triggered = 0;
+
+static idevice_t device = NULL;
+static syslog_relay_client_t syslog = NULL;
+
+static const char QUIET_FILTER[] = "CircleJoinRequested|CommCenter|HeuristicInterpreter|MobileMail|PowerUIAgent|ProtectedCloudKeySyncing|SpringBoard|UserEventAgent|WirelessRadioManagerd|accessoryd|accountsd|aggregated|analyticsd|appstored|apsd|assetsd|assistant_service|backboardd|biometrickitd|bluetoothd|calaccessd|callservicesd|cloudd|com.apple.Safari.SafeBrowsing.Service|contextstored|corecaptured|coreduetd|corespeechd|cdpd|dasd|dataaccessd|distnoted|dprivacyd|duetexpertd|findmydeviced|fmfd|fmflocatord|gpsd|healthd|homed|identityservicesd|imagent|itunescloudd|itunesstored|kernel|locationd|maild|mDNSResponder|mediaremoted|mediaserverd|mobileassetd|nanoregistryd|nanotimekitcompaniond|navd|nsurlsessiond|passd|pasted|photoanalysisd|powerd|powerlogHelperd|ptpd|rapportd|remindd|routined|runningboardd|searchd|sharingd|suggestd|symptomsd|timed|thermalmonitord|useractivityd|vmd|wifid|wirelessproxd";
+
+static int use_network = 0;
+
+static char *line = NULL;
+static int line_buffer_size = 0;
+static int lp = 0;
+
+static void add_filter(const char* filterstr)
+{
+ int filter_len = strlen(filterstr);
+ const char* start = filterstr;
+ const char* end = filterstr + filter_len;
+ const char* p = start;
+ while (p <= end) {
+ if ((*p == '|') || (*p == '\0')) {
+ if (p-start > 0) {
+ char* procn = malloc(p-start+1);
+ if (!procn) {
+ fprintf(stderr, "ERROR: malloc() failed\n");
+ exit(EXIT_FAILURE);
+ }
+ memcpy(procn, start, p-start);
+ procn[p-start] = '\0';
+ char* endp = NULL;
+ int pid_value = (int)strtol(procn, &endp, 10);
+ if (!endp || *endp == 0) {
+ int *new_pid_filters = realloc(pid_filters, sizeof(int) * (num_pid_filters+1));
+ if (!new_pid_filters) {
+ fprintf(stderr, "ERROR: realloc() failed\n");
+ exit(EXIT_FAILURE);
+ }
+ pid_filters = new_pid_filters;
+ pid_filters[num_pid_filters] = pid_value;
+ num_pid_filters++;
+ } else {
+ char **new_proc_filters = realloc(proc_filters, sizeof(char*) * (num_proc_filters+1));
+ if (!new_proc_filters) {
+ fprintf(stderr, "ERROR: realloc() failed\n");
+ exit(EXIT_FAILURE);
+ }
+ proc_filters = new_proc_filters;
+ proc_filters[num_proc_filters] = procn;
+ num_proc_filters++;
+ }
+ }
+ start = p+1;
+ }
+ p++;
+ }
+}
+
+static int find_char(char c, char** p, const char* end)
+{
+ while ((**p != c) && (*p < end)) {
+ (*p)++;
+ }
+ return (**p == c);
+}
+
+static void stop_logging(void);
+
+static void syslog_callback(char c, void *user_data)
+{
+ if (lp >= line_buffer_size-1) {
+ line_buffer_size+=1024;
+ char* _line = realloc(line, line_buffer_size);
+ if (!_line) {
+ fprintf(stderr, "ERROR: realloc failed\n");
+ exit(EXIT_FAILURE);
+ }
+ line = _line;
+ }
+ line[lp++] = c;
+ if (c == '\0') {
+ int shall_print = 0;
+ int trigger_off = 0;
+ lp--;
+ char* linep = &line[0];
+ do {
+ if (lp < 16) {
+ shall_print = 1;
+ cprintf(FG_WHITE);
+ break;
+ }
+
+ if (line[3] == ' ' && line[6] == ' ' && line[15] == ' ') {
+ char* end = &line[lp];
+ char* p = &line[16];
+
+ /* device name */
+ char* device_name_start = p;
+ char* device_name_end = p;
+ if (!find_char(' ', &p, end)) break;
+ device_name_end = p;
+ p++;
+
+ /* check if we have any triggers/untriggers */
+ if (num_untrigger_filters > 0 && triggered) {
+ int found = 0;
+ int i;
+ for (i = 0; i < num_untrigger_filters; i++) {
+ if (strstr(device_name_end+1, untrigger_filters[i])) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ shall_print = 1;
+ } else {
+ shall_print = 1;
+ trigger_off = 1;
+ }
+ } else if (num_trigger_filters > 0 && !triggered) {
+ int found = 0;
+ int i;
+ for (i = 0; i < num_trigger_filters; i++) {
+ if (strstr(device_name_end+1, trigger_filters[i])) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ shall_print = 0;
+ break;
+ }
+ triggered = 1;
+ shall_print = 1;
+ } else if (num_trigger_filters == 0 && num_untrigger_filters > 0 && !triggered) {
+ shall_print = 0;
+ quit_flag++;
+ break;
+ }
+
+ /* check message filters */
+ if (num_msg_filters > 0) {
+ int found = 0;
+ int i;
+ for (i = 0; i < num_msg_filters; i++) {
+ if (strstr(device_name_end+1, msg_filters[i])) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ shall_print = 0;
+ break;
+ }
+ shall_print = 1;
+ }
+
+ /* process name */
+ char* proc_name_start = p;
+ char* proc_name_end = p;
+ if (!find_char('[', &p, end)) break;
+ char* process_name_start = proc_name_start;
+ char* process_name_end = p;
+ char* pid_start = p+1;
+ char* pp = process_name_start;
+ if (find_char('(', &pp, p)) {
+ process_name_end = pp;
+ }
+ if (!find_char(']', &p, end)) break;
+ p++;
+ if (*p != ' ') break;
+ proc_name_end = p;
+ p++;
+
+ int proc_matched = 0;
+ if (num_pid_filters > 0) {
+ char* endp = NULL;
+ int pid_value = (int)strtol(pid_start, &endp, 10);
+ if (endp && (*endp == ']')) {
+ int found = proc_filter_excluding;
+ int i = 0;
+ for (i = 0; i < num_pid_filters; i++) {
+ if (pid_value == pid_filters[i]) {
+ found = !proc_filter_excluding;
+ break;
+ }
+ }
+ if (found) {
+ proc_matched = 1;
+ }
+ }
+ }
+ if (num_proc_filters > 0 && !proc_matched) {
+ int found = proc_filter_excluding;
+ int i = 0;
+ for (i = 0; i < num_proc_filters; i++) {
+ if (!proc_filters[i]) continue;
+ if (strncmp(proc_filters[i], process_name_start, process_name_end-process_name_start) == 0) {
+ found = !proc_filter_excluding;
+ break;
+ }
+ }
+ if (found) {
+ proc_matched = 1;
+ }
+ }
+ if (proc_matched) {
+ shall_print = 1;
+ } else {
+ if (num_pid_filters > 0 || num_proc_filters > 0) {
+ shall_print = 0;
+ break;
+ }
+ }
+
+ /* log level */
+ char* level_start = p;
+ char* level_end = p;
+ const char* level_color = NULL;
+ if (!strncmp(p, "<Notice>:", 9)) {
+ level_end += 9;
+ level_color = FG_GREEN;
+ } else if (!strncmp(p, "<Error>:", 8)) {
+ level_end += 8;
+ level_color = FG_RED;
+ } else if (!strncmp(p, "<Warning>:", 10)) {
+ level_end += 10;
+ level_color = FG_YELLOW;
+ } else if (!strncmp(p, "<Debug>:", 8)) {
+ level_end += 8;
+ level_color = FG_MAGENTA;
+ } else {
+ level_color = FG_WHITE;
+ }
+
+ /* write date and time */
+ cprintf(FG_LIGHT_GRAY);
+ fwrite(line, 1, 16, stdout);
+
+ if (show_device_name) {
+ /* write device name */
+ cprintf(FG_DARK_YELLOW);
+ fwrite(device_name_start, 1, device_name_end-device_name_start+1, stdout);
+ cprintf(COLOR_RESET);
+ }
+
+ /* write process name */
+ cprintf(FG_BRIGHT_CYAN);
+ fwrite(process_name_start, 1, process_name_end-process_name_start, stdout);
+ cprintf(FG_CYAN);
+ fwrite(process_name_end, 1, proc_name_end-process_name_end+1, stdout);
+
+ /* write log level */
+ cprintf(level_color);
+ if (level_end > level_start) {
+ fwrite(level_start, 1, level_end-level_start, stdout);
+ p = level_end;
+ }
+
+ lp -= p - linep;
+ linep = p;
+
+ cprintf(FG_WHITE);
+
+ } else {
+ shall_print = 1;
+ cprintf(FG_WHITE);
+ }
+ } while (0);
+
+ if ((num_msg_filters == 0 && num_proc_filters == 0 && num_pid_filters == 0 && num_trigger_filters == 0 && num_untrigger_filters == 0) || shall_print) {
+ fwrite(linep, 1, lp, stdout);
+ cprintf(COLOR_RESET);
+ fflush(stdout);
+ if (trigger_off) {
+ triggered = 0;
+ }
+ }
+ line[0] = '\0';
+ lp = 0;
+ return;
+ }
+}
+
+static int start_logging(void)
+{
+ idevice_error_t ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX);
+ if (ret != IDEVICE_E_SUCCESS) {
+ fprintf(stderr, "Device with udid %s not found!?\n", udid);
+ return -1;
+ }
+
+ lockdownd_client_t lockdown = NULL;
+ lockdownd_error_t lerr = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME);
+ if (lerr != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "ERROR: Could not connect to lockdownd: %d\n", lerr);
+ idevice_free(device);
+ device = NULL;
+ return -1;
+ }
+
+ /* start syslog_relay service */
+ lockdownd_service_descriptor_t svc = NULL;
+ lerr = lockdownd_start_service(lockdown, SYSLOG_RELAY_SERVICE_NAME, &svc);
+ if (lerr == LOCKDOWN_E_PASSWORD_PROTECTED) {
+ fprintf(stderr, "*** Device is passcode protected, enter passcode on the device to continue ***\n");
+ while (!quit_flag) {
+ lerr = lockdownd_start_service(lockdown, SYSLOG_RELAY_SERVICE_NAME, &svc);
+ if (lerr != LOCKDOWN_E_PASSWORD_PROTECTED) {
+ break;
+ }
+ sleep(1);
+ }
+ }
+ if (lerr != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "ERROR: Could not connect to lockdownd: %d\n", lerr);
+ idevice_free(device);
+ device = NULL;
+ return -1;
+ }
+ lockdownd_client_free(lockdown);
+
+ /* connect to syslog_relay service */
+ syslog_relay_error_t serr = SYSLOG_RELAY_E_UNKNOWN_ERROR;
+ serr = syslog_relay_client_new(device, svc, &syslog);
+ lockdownd_service_descriptor_free(svc);
+ if (serr != SYSLOG_RELAY_E_SUCCESS) {
+ fprintf(stderr, "ERROR: Could not start service com.apple.syslog_relay.\n");
+ idevice_free(device);
+ device = NULL;
+ return -1;
+ }
+
+ /* start capturing syslog */
+ serr = syslog_relay_start_capture_raw(syslog, syslog_callback, NULL);
+ if (serr != SYSLOG_RELAY_E_SUCCESS) {
+ fprintf(stderr, "ERROR: Unable tot start capturing syslog.\n");
+ syslog_relay_client_free(syslog);
+ syslog = NULL;
+ idevice_free(device);
+ device = NULL;
+ return -1;
+ }
+
+ fprintf(stdout, "[connected:%s]\n", udid);
+ fflush(stdout);
+
+ return 0;
+}
+
+static void stop_logging(void)
+{
+ fflush(stdout);
-void print_usage(int argc, char **argv);
+ if (syslog) {
+ syslog_relay_client_free(syslog);
+ syslog = NULL;
+ }
+
+ if (device) {
+ idevice_free(device);
+ device = NULL;
+ }
+}
+
+static void device_event_cb(const idevice_event_t* event, void* userdata)
+{
+ if (use_network && event->conn_type != CONNECTION_NETWORK) {
+ return;
+ }
+ if (!use_network && event->conn_type != CONNECTION_USBMUXD) {
+ return;
+ }
+ if (event->event == IDEVICE_DEVICE_ADD) {
+ if (!syslog) {
+ if (!udid) {
+ udid = strdup(event->udid);
+ }
+ if (strcmp(udid, event->udid) == 0) {
+ if (start_logging() != 0) {
+ fprintf(stderr, "Could not start logger for udid %s\n", udid);
+ }
+ }
+ }
+ } else if (event->event == IDEVICE_DEVICE_REMOVE) {
+ if (syslog && (strcmp(udid, event->udid) == 0)) {
+ stop_logging();
+ fprintf(stdout, "[disconnected:%s]\n", udid);
+ if (exit_on_disconnect) {
+ quit_flag++;
+ }
+ }
+ }
+}
/**
* signal handler function for cleaning up properly
*/
static void clean_exit(int sig)
{
- fprintf(stderr, "Exiting...\n");
+ fprintf(stderr, "\nExiting...\n");
quit_flag++;
}
+static void print_usage(int argc, char **argv, int is_error)
+{
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Relay syslog of a connected device.\n"
+ "\n"
+ "OPTIONS:\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ " -n, --network connect to network device\n"
+ " -x, --exit exit when device disconnects\n"
+ " -h, --help prints usage information\n"
+ " -d, --debug enable communication debugging\n"
+ " -v, --version prints version information\n"
+ " --no-colors disable colored output\n"
+ " -o, --output FILE write to FILE instead of stdout\n"
+ " (existing FILE will be overwritten)\n"
+ " --colors force writing colored output, e.g. for --output\n"
+ "\n"
+ "FILTER OPTIONS:\n"
+ " -m, --match STRING only print messages that contain STRING\n"
+ " -t, --trigger STRING start logging when matching STRING\n"
+ " -T, --untrigger STRING stop logging when matching STRING\n"
+ " -p, --process PROCESS only print messages from matching process(es)\n"
+ " -e, --exclude PROCESS print all messages except matching process(es)\n"
+ " PROCESS is a process name or multiple process names\n"
+ " separated by \"|\".\n"
+ " -q, --quiet set a filter to exclude common noisy processes\n"
+ " --quiet-list prints the list of processes for --quiet and exits\n"
+ " -k, --kernel only print kernel messages\n"
+ " -K, --no-kernel suppress kernel messages\n"
+ "\n"
+ "For filter examples consult idevicesyslog(1) man page.\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
+}
+
int main(int argc, char *argv[])
{
- lockdownd_client_t client = NULL;
- idevice_t phone = NULL;
- idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR;
- int i;
- char uuid[41];
- uint16_t port = 0;
- uuid[0] = 0;
+ int include_filter = 0;
+ int exclude_filter = 0;
+ int include_kernel = 0;
+ int exclude_kernel = 0;
+ int force_colors = 0;
+ int c = 0;
+ const struct option longopts[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+ { "network", no_argument, NULL, 'n' },
+ { "exit", no_argument, NULL, 'x' },
+ { "trigger", required_argument, NULL, 't' },
+ { "untrigger", required_argument, NULL, 'T' },
+ { "match", required_argument, NULL, 'm' },
+ { "process", required_argument, NULL, 'p' },
+ { "exclude", required_argument, NULL, 'e' },
+ { "quiet", no_argument, NULL, 'q' },
+ { "kernel", no_argument, NULL, 'k' },
+ { "no-kernel", no_argument, NULL, 'K' },
+ { "quiet-list", no_argument, NULL, 1 },
+ { "no-colors", no_argument, NULL, 2 },
+ { "colors", no_argument, NULL, 3 },
+ { "output", required_argument, NULL, 'o' },
+ { "version", no_argument, NULL, 'v' },
+ { NULL, 0, NULL, 0}
+ };
signal(SIGINT, clean_exit);
- signal(SIGQUIT, 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")) {
+ while ((c = getopt_long(argc, argv, "dhu:nxt:T:m:e:p:qkKo:v", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
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);
+ break;
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ free(udid);
+ udid = strdup(optarg);
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 'q':
+ exclude_filter++;
+ add_filter(QUIET_FILTER);
+ break;
+ case 'p':
+ case 'e':
+ if (c == 'p') {
+ include_filter++;
+ } else if (c == 'e') {
+ exclude_filter++;
+ }
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: filter string must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ add_filter(optarg);
+ break;
+ case 'm':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: message filter string must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ } else {
+ char **new_msg_filters = realloc(msg_filters, sizeof(char*) * (num_msg_filters+1));
+ if (!new_msg_filters) {
+ fprintf(stderr, "ERROR: realloc() failed\n");
+ exit(EXIT_FAILURE);
+ }
+ msg_filters = new_msg_filters;
+ msg_filters[num_msg_filters] = strdup(optarg);
+ num_msg_filters++;
+ }
+ break;
+ case 't':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: trigger filter string must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ } else {
+ char **new_trigger_filters = realloc(trigger_filters, sizeof(char*) * (num_trigger_filters+1));
+ if (!new_trigger_filters) {
+ fprintf(stderr, "ERROR: realloc() failed\n");
+ exit(EXIT_FAILURE);
+ }
+ trigger_filters = new_trigger_filters;
+ trigger_filters[num_trigger_filters] = strdup(optarg);
+ num_trigger_filters++;
+ }
+ break;
+ case 'T':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: untrigger filter string must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ } else {
+ char **new_untrigger_filters = realloc(untrigger_filters, sizeof(char*) * (num_untrigger_filters+1));
+ if (!new_untrigger_filters) {
+ fprintf(stderr, "ERROR: realloc() failed\n");
+ exit(EXIT_FAILURE);
+ }
+ untrigger_filters = new_untrigger_filters;
+ untrigger_filters[num_untrigger_filters] = strdup(optarg);
+ num_untrigger_filters++;
+ }
+ break;
+ case 'k':
+ include_kernel++;
+ break;
+ case 'K':
+ exclude_kernel++;
+ break;
+ case 'x':
+ exit_on_disconnect = 1;
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
+ return 0;
+ case 1: {
+ printf("%s\n", QUIET_FILTER);
return 0;
}
- else {
- print_usage(argc, argv);
+ case 2:
+ term_colors_set_enabled(0);
+ break;
+ case 3:
+ force_colors = 1;
+ break;
+ case 'o':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: --output option requires an argument!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ } else {
+ if (freopen(optarg, "w", stdout) == NULL) {
+ fprintf(stderr, "ERROR: Failed to open output file '%s' for writing: %s\n", optarg, strerror(errno));
+ return 1;
+ }
+ term_colors_set_enabled(0);
+ }
+ break;
+ case 'v':
+ printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
return 0;
+ default:
+ print_usage(argc, argv, 1);
+ return 2;
}
}
- 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;
- }
+ if (force_colors) {
+ term_colors_set_enabled(1);
}
- else
- {
- ret = idevice_new(&phone, NULL);
- if (ret != IDEVICE_E_SUCCESS) {
- printf("No device found, is it plugged in?\n");
- return -1;
- }
+
+ if (include_kernel > 0 && exclude_kernel > 0) {
+ fprintf(stderr, "ERROR: -k and -K cannot be used together.\n");
+ print_usage(argc, argv, 1);
+ return 2;
}
- if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "idevicesyslog")) {
- idevice_free(phone);
- return -1;
+ if (include_filter > 0 && exclude_filter > 0) {
+ fprintf(stderr, "ERROR: -p and -e/-q cannot be used together.\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ if (include_filter > 0 && exclude_kernel > 0) {
+ fprintf(stderr, "ERROR: -p and -K cannot be used together.\n");
+ print_usage(argc, argv, 1);
+ return 2;
}
- /* start syslog_relay service and retrieve port */
- ret = lockdownd_start_service(client, "com.apple.syslog_relay", &port);
- if ((ret == LOCKDOWN_E_SUCCESS) && port) {
- lockdownd_client_free(client);
-
- /* connect to socket relay messages */
- idevice_connection_t conn = NULL;
- if ((idevice_connect(phone, port, &conn) != IDEVICE_E_SUCCESS) || !conn) {
- printf("ERROR: Could not open usbmux connection.\n");
- } else {
- while (!quit_flag) {
- char *receive = NULL;
- uint32_t datalen = 0, bytes = 0, recv_bytes = 0;
-
- ret = idevice_connection_receive(conn, (char *) &datalen, sizeof(datalen), &bytes);
- if (ret < 0) {
- fprintf(stderr, "Error receiving data. Exiting...\n");
- break;
+ if (exclude_filter > 0) {
+ proc_filter_excluding = 1;
+ if (include_kernel) {
+ int i = 0;
+ for (i = 0; i < num_proc_filters; i++) {
+ if (!strcmp(proc_filters[i], "kernel")) {
+ free(proc_filters[i]);
+ proc_filters[i] = NULL;
}
+ }
+ } else if (exclude_kernel) {
+ add_filter("kernel");
+ }
+ } else {
+ if (include_kernel) {
+ add_filter("kernel");
+ } else if (exclude_kernel) {
+ proc_filter_excluding = 1;
+ add_filter("kernel");
+ }
+ }
- datalen = GUINT32_FROM_BE(datalen);
+ if (num_untrigger_filters > 0 && num_trigger_filters == 0) {
+ triggered = 1;
+ }
- if (datalen == 0)
- continue;
+ argc -= optind;
+ argv += optind;
- recv_bytes += bytes;
- receive = (char *) malloc(sizeof(char) * datalen);
+ int num = 0;
+ idevice_info_t *devices = NULL;
+ idevice_get_device_list_extended(&devices, &num);
+ idevice_device_list_extended_free(devices);
+ if (num == 0) {
+ if (!udid) {
+ fprintf(stderr, "No device found. Plug in a device or pass UDID with -u to wait for device to be available.\n");
+ return -1;
+ }
- while (!quit_flag && (recv_bytes <= datalen)) {
- ret = idevice_connection_receive(conn, receive, datalen, &bytes);
+ fprintf(stderr, "Waiting for device with UDID %s to become available...\n", udid);
+ }
- if (bytes == 0)
- break;
+ line_buffer_size = 1024;
+ line = malloc(line_buffer_size);
- recv_bytes += bytes;
+ idevice_subscription_context_t context = NULL;
+ idevice_events_subscribe(&context, device_event_cb, NULL);
- fwrite(receive, sizeof(char), bytes, stdout);
- }
+ while (!quit_flag) {
+ sleep(1);
+ }
+ idevice_events_unsubscribe(context);
+ stop_logging();
- free(receive);
- }
+ if (num_proc_filters > 0) {
+ int i;
+ for (i = 0; i < num_proc_filters; i++) {
+ free(proc_filters[i]);
}
- idevice_disconnect(conn);
- } else {
- printf("ERROR: Could not start service com.apple.syslog_relay.\n");
+ free(proc_filters);
+ }
+ if (num_pid_filters > 0) {
+ free(pid_filters);
+ }
+ if (num_msg_filters > 0) {
+ int i;
+ for (i = 0; i < num_msg_filters; i++) {
+ free(msg_filters[i]);
+ }
+ free(msg_filters);
+ }
+ if (num_trigger_filters > 0) {
+ int i;
+ for (i = 0; i < num_trigger_filters; i++) {
+ free(trigger_filters[i]);
+ }
+ free(trigger_filters);
+ }
+ if (num_untrigger_filters > 0) {
+ int i;
+ for (i = 0; i < num_untrigger_filters; i++) {
+ free(untrigger_filters[i]);
+ }
+ free(untrigger_filters);
}
- idevice_free(phone);
+ free(line);
- return 0;
-}
+ free(udid);
-void print_usage(int argc, char **argv)
-{
- char *name = NULL;
-
- name = strrchr(argv[0], '/');
- printf("Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0]));
- printf("Relay syslog of a connected iPhone/iPod Touch.\n\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");
+ return 0;
}
-