summaryrefslogtreecommitdiffstats
path: root/src/ideviceinstaller.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ideviceinstaller.c')
-rw-r--r--src/ideviceinstaller.c1756
1 files changed, 1183 insertions, 573 deletions
diff --git a/src/ideviceinstaller.c b/src/ideviceinstaller.c
index 3780979..d07cedf 100644
--- a/src/ideviceinstaller.c
+++ b/src/ideviceinstaller.c
@@ -1,7 +1,8 @@
-/**
- * ideviceinstaller -- Manage iPhone/iPod apps
+/*
+ * ideviceinstaller - Manage apps on iOS devices.
*
- * Copyright (C) 2010 Nikias Bassen <nikias@gmx.li>
+ * Copyright (C) 2010-2023 Nikias Bassen <nikias@gmx.li>
+ * Copyright (C) 2010-2015 Martin Szulecki <m.szulecki@libimobiledevice.org>
*
* Licensed under the GNU General Public License Version 2
*
@@ -17,7 +18,7 @@
*
* 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
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#ifdef HAVE_CONFIG_H
@@ -33,6 +34,15 @@
#include <time.h>
#include <libgen.h>
#include <inttypes.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifndef WIN32
+#include <signal.h>
+#endif
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/lockdown.h>
@@ -44,93 +54,211 @@
#include <zip.h>
+#ifdef WIN32
+#include <windows.h>
+#define wait_ms(x) Sleep(x)
+#else
+#define wait_ms(x) { struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = x * 1000000; nanosleep(&ts, NULL); }
+#endif
+
+#ifndef HAVE_VASPRINTF
+static int vasprintf(char **PTR, const char *TEMPLATE, va_list AP)
+{
+ int res;
+ char buf[16];
+ res = vsnprintf(buf, 16, TEMPLATE, AP);
+ if (res > 0) {
+ *PTR = (char*)malloc(res+1);
+ res = vsnprintf(*PTR, res+1, TEMPLATE, AP);
+ }
+ return res;
+}
+#endif
+
+#ifndef HAVE_ASPRINTF
+static int asprintf(char **PTR, const char *TEMPLATE, ...)
+{
+ int res;
+ va_list AP;
+ va_start(AP, TEMPLATE);
+ res = vasprintf(PTR, TEMPLATE, AP);
+ va_end(AP);
+ return res;
+}
+#endif
+
+#define ITUNES_METADATA_PLIST_FILENAME "iTunesMetadata.plist"
+
const char PKG_PATH[] = "PublicStaging";
const char APPARCH_PATH[] = "ApplicationArchives";
-char *uuid = NULL;
-char *options = NULL;
-char *appid = NULL;
-
-int list_apps_mode = 0;
-int install_mode = 0;
-int uninstall_mode = 0;
-int upgrade_mode = 0;
-int list_archives_mode = 0;
-int archive_mode = 0;
-int restore_mode = 0;
-int remove_archive_mode = 0;
+char *udid = NULL;
+char *cmdarg = NULL;
+char *extsinf = NULL;
+char *extmeta = NULL;
+
+enum cmd_mode {
+ CMD_NONE = 0,
+ CMD_LIST_APPS,
+ CMD_INSTALL,
+ CMD_UNINSTALL,
+ CMD_UPGRADE,
+ CMD_LIST_ARCHIVES,
+ CMD_ARCHIVE,
+ CMD_RESTORE,
+ CMD_REMOVE_ARCHIVE
+};
+
+int cmd = CMD_NONE;
char *last_status = NULL;
-int wait_for_op_complete = 0;
+int wait_for_command_complete = 0;
+int use_network = 0;
+int use_notifier = 0;
int notification_expected = 0;
-int op_completed = 0;
-int err_occured = 0;
+int is_device_connected = 0;
+int command_completed = 0;
+int ignore_events = 0;
+int err_occurred = 0;
int notified = 0;
+plist_t bundle_ids = NULL;
+plist_t return_attrs = NULL;
+#define FORMAT_XML 1
+#define FORMAT_JSON 2
+int output_format = 0;
+int opt_list_user = 0;
+int opt_list_system = 0;
+char *copy_path = NULL;
+int remove_after_copy = 0;
+int skip_uninstall = 1;
+int app_only = 0;
+int docs_only = 0;
+
+static void print_apps_header()
+{
+ if (!return_attrs) {
+ return;
+ }
+ uint32_t i = 0;
+ for (i = 0; i < plist_array_get_size(return_attrs); i++) {
+ plist_t node = plist_array_get_item(return_attrs, i);
+ if (i > 0) {
+ printf(", ");
+ }
+ printf("%s", plist_get_string_ptr(node, NULL));
+ }
+ printf("\n");
+}
+
+static void print_apps(plist_t apps)
+{
+ if (!return_attrs) {
+ return;
+ }
+ uint32_t i = 0;
+ for (i = 0; i < plist_array_get_size(apps); i++) {
+ plist_t app = plist_array_get_item(apps, i);
+ uint32_t j = 0;
+ for (j = 0; j < plist_array_get_size(return_attrs); j++) {
+ plist_t node = plist_array_get_item(return_attrs, j);
+ if (j > 0) {
+ printf(", ");
+ }
+ const char* key = plist_get_string_ptr(node, NULL);
+ node = plist_dict_get_item(app, key);
+ if (node) {
+ if (!strcmp(key, "CFBundleIdentifier")) {
+ printf("%s", plist_get_string_ptr(node, NULL));
+ } else {
+ plist_write_to_stream(node, stdout, PLIST_FORMAT_PRINT, PLIST_OPT_NO_NEWLINE);
+ }
+ }
+ }
+ printf("\n");
+ }
+}
-#ifdef HAVE_LIBIMOBILEDEVICE_1_0
static void notifier(const char *notification, void *unused)
-#else
-static void notifier(const char *notification)
-#endif
{
- /* printf("notification received: %s\n", notification);*/
notified = 1;
}
-#ifdef HAVE_LIBIMOBILEDEVICE_1_1
-static void status_cb(const char *operation, plist_t status, void *unused)
-#else
-static void status_cb(const char *operation, plist_t status)
-#endif
+static void status_cb(plist_t command, plist_t status, void *unused)
{
- if (status && operation) {
- plist_t npercent = plist_dict_get_item(status, "PercentComplete");
- plist_t nstatus = plist_dict_get_item(status, "Status");
- plist_t nerror = plist_dict_get_item(status, "Error");
- int percent = 0;
- char *status_msg = NULL;
- if (npercent) {
- uint64_t val = 0;
- plist_get_uint_val(npercent, &val);
- percent = val;
- }
- if (nstatus) {
- plist_get_string_val(nstatus, &status_msg);
- if (!strcmp(status_msg, "Complete")) {
- op_completed = 1;
+ if (command && status) {
+ char* command_name = NULL;
+ instproxy_command_get_name(command, &command_name);
+
+ /* get status */
+ char *status_name = NULL;
+ instproxy_status_get_name(status, &status_name);
+
+ if (status_name) {
+ if (!strcmp(status_name, "Complete")) {
+ command_completed = 1;
}
}
- if (!nerror) {
- if (last_status && (strcmp(last_status, status_msg))) {
- printf("\n");
- }
- if (!npercent) {
- printf("%s - %s\n", operation, status_msg);
- } else {
- printf("%s - %s (%d%%)\r", operation, status_msg, percent);
+ /* get error if any */
+ char* error_name = NULL;
+ char* error_description = NULL;
+ uint64_t error_code = 0;
+ instproxy_status_get_error(status, &error_name, &error_description, &error_code);
+
+ /* output/handling */
+ if (!error_name) {
+ if (!strcmp(command_name, "Browse")) {
+ uint64_t total = 0;
+ uint64_t current_index = 0;
+ uint64_t current_amount = 0;
+ plist_t current_list = NULL;
+ instproxy_status_get_current_list(status, &total, &current_index, &current_amount, &current_list);
+ if (current_list) {
+ print_apps(current_list);
+ plist_free(current_list);
+ }
+ } else if (status_name) {
+ /* get progress if any */
+ int percent = -1;
+ instproxy_status_get_percent_complete(status, &percent);
+
+ if (last_status && (strcmp(last_status, status_name))) {
+ printf("\n");
+ }
+
+ if (percent >= 0) {
+ printf("\r%s: %s (%d%%)", command_name, status_name, percent);
+ } else {
+ printf("\r%s: %s", command_name, status_name);
+ }
+ if (command_completed) {
+ printf("\n");
+ }
}
} else {
- char *err_msg = NULL;
- plist_get_string_val(nerror, &err_msg);
- printf("%s - Error occured: %s\n", operation, err_msg);
- free(err_msg);
- err_occured = 1;
- }
- if (last_status) {
- free(last_status);
- last_status = NULL;
- }
- if (status_msg) {
- last_status = strdup(status_msg);
- free(status_msg);
+ /* report error to the user */
+ if (error_description)
+ fprintf(stderr, "ERROR: %s failed. Got error \"%s\" with code 0x%08"PRIx64": %s\n", command_name, error_name, error_code, error_description ? error_description: "N/A");
+ else
+ fprintf(stderr, "ERROR: %s failed. Got error \"%s\".\n", command_name, error_name);
+ err_occurred = 1;
}
+
+ /* clean up */
+ free(error_name);
+ free(error_description);
+
+ free(last_status);
+ last_status = status_name;
+
+ free(command_name);
+ command_name = NULL;
} else {
- printf("%s: called with invalid data!\n", __func__);
+ fprintf(stderr, "ERROR: %s was called with invalid arguments!\n", __func__);
}
}
-static int zip_f_get_contents(struct zip *zf, const char *filename, int locate_flags, char **buffer, uint32_t *len)
+static int zip_get_contents(struct zip *zf, const char *filename, int locate_flags, char **buffer, uint32_t *len)
{
struct zip_stat zs;
struct zip_file *zfile;
@@ -140,7 +268,6 @@ static int zip_f_get_contents(struct zip *zf, const char *filename, int locate_f
*len = 0;
if (zindex < 0) {
- fprintf(stderr, "ERROR: could not locate %s in archive!\n", filename);
return -1;
}
@@ -163,7 +290,7 @@ static int zip_f_get_contents(struct zip *zf, const char *filename, int locate_f
}
*buffer = malloc(zs.size);
- if (zip_fread(zfile, *buffer, zs.size) != zs.size) {
+ if (zs.size > LLONG_MAX || zip_fread(zfile, *buffer, zs.size) != (zip_int64_t)zs.size) {
fprintf(stderr, "ERROR: zip_fread %" PRIu64 " bytes from '%s'\n", (uint64_t)zs.size, filename);
free(*buffer);
*buffer = NULL;
@@ -175,572 +302,1096 @@ static int zip_f_get_contents(struct zip *zf, const char *filename, int locate_f
return 0;
}
-static void do_wait_when_needed()
+static int zip_get_app_directory(struct zip* zf, char** path)
{
- int i = 0;
- struct timespec ts;
- ts.tv_sec = 0;
- ts.tv_nsec = 500000000;
-
- /* wait for operation to complete */
- while (wait_for_op_complete && !op_completed && !err_occured
- && !notified && (i < 60)) {
- nanosleep(&ts, NULL);
- i++;
+ zip_int64_t i = 0;
+ zip_int64_t c = (zip_int64_t)zip_get_num_entries(zf, 0);
+ int len = 0;
+ const char* name = NULL;
+
+ /* look through all filenames in the archive */
+ do {
+ /* get filename at current index */
+ name = zip_get_name(zf, i++, 0);
+ if (name != NULL) {
+ /* check if we have a "Payload/.../" name */
+ len = strlen(name);
+ if (!strncmp(name, "Payload/", 8) && (len > 8)) {
+ /* skip hidden files */
+ if (name[8] == '.')
+ continue;
+
+ /* locate the second directory delimiter */
+ const char* p = name + 8;
+ do {
+ if (*p == '/') {
+ break;
+ }
+ } while(p++ != NULL);
+
+ /* try next entry if not found */
+ if (p == NULL)
+ continue;
+
+ len = p - name + 1;
+
+ /* make sure app directory endwith .app */
+ if (len < 12 || strncmp(p - 4, ".app", 4))
+ {
+ continue;
+ }
+
+ if (path != NULL) {
+ free(*path);
+ *path = NULL;
+ }
+
+ /* allocate and copy filename */
+ *path = (char*)malloc(len + 1);
+ strncpy(*path, name, len);
+
+ /* add terminating null character */
+ char* t = *path + len;
+ *t = '\0';
+ break;
+ }
+ }
+ } while(i < c);
+
+ if (*path == NULL) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static void idevice_event_callback(const idevice_event_t* event, void* userdata)
+{
+ if (ignore_events) {
+ return;
+ }
+ if (event->event == IDEVICE_DEVICE_REMOVE) {
+ if (!strcmp(udid, event->udid)) {
+ fprintf(stderr, "ideviceinstaller: Device removed\n");
+ is_device_connected = 0;
+ }
+ }
+}
+
+static void idevice_wait_for_command_to_complete()
+{
+ is_device_connected = 1;
+ ignore_events = 0;
+
+ /* subscribe to make sure to exit on device removal */
+ idevice_event_subscribe(idevice_event_callback, NULL);
+
+ /* wait for command to complete */
+ while (wait_for_command_complete && !command_completed && !err_occurred
+ && is_device_connected) {
+ wait_ms(50);
}
/* wait some time if a notification is expected */
- while (notification_expected && !notified && !err_occured && (i < 10)) {
- nanosleep(&ts, NULL);
- i++;
+ while (use_notifier && notification_expected && !notified && !err_occurred && is_device_connected) {
+ wait_ms(50);
}
+
+ ignore_events = 1;
+ idevice_event_unsubscribe();
}
-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("Manage apps on an iDevice.\n\n");
- printf
- (" -U, --uuid UUID\tTarget specific device by its 40-digit device UUID.\n"
- " -l, --list-apps\tList apps, possible options:\n"
- " -o list_user\t- list user apps only (this is the default)\n"
- " -o list_system\t- list system apps only\n"
- " -o list_all\t- list all types of apps\n"
- " -o xml\t\t- print full output as xml plist\n"
- " -i, --install ARCHIVE\tInstall app from package file specified by ARCHIVE.\n"
- " -u, --uninstall APPID\tUninstall app specified by APPID.\n"
- " -g, --upgrade APPID\tUpgrade app specified by APPID.\n"
- " -L, --list-archives\tList archived applications, possible options:\n"
- " -o xml\t\t- print full output as xml plist\n"
- " -a, --archive APPID\tArchive app specified by APPID, possible options:\n"
- " -o uninstall\t- uninstall the package after making an archive\n"
- " -o app_only\t- archive application data only\n"
- " -o copy=PATH\t- copy the app archive to directory PATH when done\n"
- " -o remove\t- only valid when copy=PATH is used: remove after copy\n"
- " -r, --restore APPID\tRestore archived app specified by APPID\n"
- " -R, --remove-archive APPID Remove app archive specified by APPID\n"
- " -o, --options\t\tPass additional options to the specified command.\n"
- " -h, --help\t\tprints usage information\n"
- " -d, --debug\t\tenable communication debugging\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"
+ "Manage apps on iOS devices.\n"
+ "\n"
+ "COMMANDS:\n"
+ " list List installed apps. Options:\n"
+ " --user List user apps only (this is the default)\n"
+ " --system List system apps only\n"
+ " --all List all types of apps\n"
+ " --xml Print output as XML Property List\n"
+ " -a, --attribute ATTR Specify attribute to return - see man page\n"
+ " (can be passed multiple times)\n"
+ " -b, --bundle-identifier BUNDLEID Only query given bundle identifier\n"
+ " (can be passed multiple times)\n"
+ " install PATH Install app from package file specified by PATH.\n"
+ " PATH can also be a .ipcc file for carrier bundles.\n"
+ " -s, --sinf PATH Pass an external SINF file\n"
+ " -m, --metadata PATH Pass an external iTunesMetadata file\n"
+ " uninstall BUNDLEID Uninstall app specified by BUNDLEID.\n"
+ " upgrade PATH Upgrade app from package file specified by PATH.\n"
+ "\n"
+ "LEGACY COMMANDS (non-functional with iOS 7 or later):\n"
+ " archive BUNDLEID Archive app specified by BUNDLEID. Options:\n"
+ " --uninstall Uninstall the package after making an archive\n"
+ " --app-only Archive application data only\n"
+ " --docs-only Archive documents (user data) only\n"
+ " --copy=PATH Copy the app archive to directory PATH when done\n"
+ " --remove Only valid when copy=PATH is used: remove after copy\n"
+ " restore BUNDLEID Restore archived app specified by BUNDLEID\n"
+ " list-archives List archived apps. Options:\n"
+ " --xml Print output as XML Property List\n"
+ " remove-archive BUNDLEID Remove app archive specified by BUNDLEID\n"
+ "\n"
+ "OPTIONS:\n"
+ " -u, --udid UDID Target specific device by UDID\n"
+ " -n, --network Connect to network device\n"
+ " -w, --notify-wait Wait for app installed/uninstalled notification\n"
+ " before reporting success of operation\n"
+ " -h, --help Print usage information\n"
+ " -d, --debug Enable communication debugging\n"
+ " -v, --version Print version information\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
}
+enum numerical_opts {
+ LIST_USER = 1,
+ LIST_SYSTEM,
+ LIST_ALL,
+ ARCHIVE_UNINSTALL,
+ ARCHIVE_APP_ONLY,
+ ARCHIVE_DOCS_ONLY,
+ ARCHIVE_COPY_PATH,
+ ARCHIVE_COPY_REMOVE,
+ OUTPUT_XML,
+ OUTPUT_JSON
+};
+
static void parse_opts(int argc, char **argv)
{
static struct option longopts[] = {
- {"help", 0, NULL, 'h'},
- {"uuid", 1, NULL, 'U'},
- {"list-apps", 0, NULL, 'l'},
- {"install", 1, NULL, 'i'},
- {"uninstall", 1, NULL, 'u'},
- {"upgrade", 1, NULL, 'g'},
- {"list-archives", 0, NULL, 'L'},
- {"archive", 1, NULL, 'a'},
- {"restore", 1, NULL, 'r'},
- {"remove-archive", 1, NULL, 'R'},
- {"options", 1, NULL, 'o'},
- {"debug", 0, NULL, 'd'},
- {NULL, 0, NULL, 0}
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+ { "network", no_argument, NULL, 'n' },
+ { "notify-wait", no_argument, NULL, 'w' },
+ { "debug", no_argument, NULL, 'd' },
+ { "version", no_argument, NULL, 'v' },
+ { "bundle-identifier", required_argument, NULL, 'b' },
+ { "attribute", required_argument, NULL, 'a' },
+ { "user", no_argument, NULL, LIST_USER },
+ { "system", no_argument, NULL, LIST_SYSTEM },
+ { "all", no_argument, NULL, LIST_ALL },
+ { "xml", no_argument, NULL, OUTPUT_XML },
+ { "json", no_argument, NULL, OUTPUT_JSON },
+ { "sinf", required_argument, NULL, 's' },
+ { "metadata", required_argument, NULL, 'm' },
+ { "uninstall", no_argument, NULL, ARCHIVE_UNINSTALL },
+ { "app-only", no_argument, NULL, ARCHIVE_APP_ONLY },
+ { "docs-only", no_argument, NULL, ARCHIVE_DOCS_ONLY },
+ { "copy", required_argument, NULL, ARCHIVE_COPY_PATH },
+ { "remove", no_argument, NULL, ARCHIVE_COPY_REMOVE },
+ { NULL, 0, NULL, 0 }
};
int c;
while (1) {
- c = getopt_long(argc, argv, "hU:li:u:g:La:r:R:o:d", longopts,
- (int *) 0);
+ c = getopt_long(argc, argv, "hu:nwdvb:a:s:m:", longopts, (int*)0);
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);
+ case 'u':
+ if (!*optarg) {
+ printf("ERROR: UDID must not be empty!\n");
+ print_usage(argc, argv, 1);
exit(2);
}
- uuid = strdup(optarg);
+ udid = strdup(optarg);
break;
- case 'l':
- list_apps_mode = 1;
- break;
- case 'i':
- install_mode = 1;
- appid = strdup(optarg);
- break;
- case 'u':
- uninstall_mode = 1;
- appid = strdup(optarg);
- break;
- case 'g':
- upgrade_mode = 1;
- appid = strdup(optarg);
- break;
- case 'L':
- list_archives_mode = 1;
+ case 'n':
+ use_network = 1;
break;
case 'a':
- archive_mode = 1;
- appid = strdup(optarg);
+ if (!*optarg) {
+ printf("ERROR: attribute must not be empty!\n");
+ print_usage(argc, argv, 1);
+ exit(2);
+ }
+ if (return_attrs == NULL) {
+ return_attrs = plist_new_array();
+ }
+ plist_array_append_item(return_attrs, plist_new_string(optarg));
break;
- case 'r':
- restore_mode = 1;
- appid = strdup(optarg);
+ case 'b':
+ if (!*optarg) {
+ printf("ERROR: bundle identifier must not be empty!\n");
+ print_usage(argc, argv, 1);
+ exit(2);
+ }
+ if (bundle_ids == NULL) {
+ bundle_ids = plist_new_array();
+ }
+ plist_array_append_item(bundle_ids, plist_new_string(optarg));
break;
- case 'R':
- remove_archive_mode = 1;
- appid = strdup(optarg);
+ case 's':
+ if (!*optarg) {
+ printf("ERROR: path for --sinf must not be empty!\n");
+ print_usage(argc, argv, 1);
+ exit(2);
+ }
+ extsinf = strdup(optarg);
break;
- case 'o':
- if (!options) {
- options = strdup(optarg);
- } else {
- char *newopts = malloc(strlen(options) + strlen(optarg) + 2);
- strcpy(newopts, options);
- free(options);
- strcat(newopts, ",");
- strcat(newopts, optarg);
- options = newopts;
+ case 'm':
+ if (!*optarg) {
+ printf("ERROR: path for --metadata must not be empty!\n");
+ print_usage(argc, argv, 1);
+ exit(2);
}
+ extmeta = strdup(optarg);
+ break;
+ case 'w':
+ use_notifier = 1;
break;
case 'd':
idevice_set_debug_level(1);
break;
+ case 'v':
+ printf("%s %s\n", PACKAGE_NAME, PACKAGE_VERSION);
+ exit(0);
+ case LIST_USER:
+ opt_list_user = 1;
+ break;
+ case LIST_SYSTEM:
+ opt_list_system = 1;
+ break;
+ case LIST_ALL:
+ opt_list_user = 1;
+ opt_list_system = 1;
+ break;
+ case OUTPUT_XML:
+ output_format = FORMAT_XML;
+ break;
+ case OUTPUT_JSON:
+ output_format = FORMAT_JSON;
+ break;
+ case ARCHIVE_UNINSTALL:
+ skip_uninstall = 0;
+ break;
+ case ARCHIVE_APP_ONLY:
+ app_only = 1;
+ docs_only = 0;
+ break;
+ case ARCHIVE_DOCS_ONLY:
+ docs_only = 1;
+ app_only = 0;
+ break;
+ case ARCHIVE_COPY_PATH:
+ copy_path = strdup(optarg);
+ break;
+ case ARCHIVE_COPY_REMOVE:
+ remove_after_copy = 1;
+ break;
default:
- print_usage(argc, argv);
+ print_usage(argc, argv, 1);
exit(2);
}
}
- if (optind <= 1 || (argc - optind > 0)) {
- print_usage(argc, argv);
+ argv += optind;
+ argc -= optind;
+
+ if (argc == 0) {
+ fprintf(stderr, "ERROR: Missing command.\n\n");
+ print_usage(argc+optind, argv-optind, 1);
exit(2);
}
+
+ char *cmdstr = argv[0];
+
+ if (!strcmp(cmdstr, "list")) {
+ cmd = CMD_LIST_APPS;
+ } else if (!strcmp(cmdstr, "install")) {
+ cmd = CMD_INSTALL;
+ } else if (!strcmp(cmdstr, "upgrade")) {
+ cmd = CMD_UPGRADE;
+ } else if (!strcmp(cmdstr, "uninstall") || !strcmp(cmdstr, "remove")) {
+ cmd = CMD_UNINSTALL;
+ } else if (!strcmp(cmdstr, "archives") || !strcmp(cmdstr, "list-archives")) {
+ cmd = CMD_LIST_ARCHIVES;
+ } else if (!strcmp(cmdstr, "archive")) {
+ cmd = CMD_ARCHIVE;
+ } else if (!strcmp(cmdstr, "restore")) {
+ cmd = CMD_RESTORE;
+ } else if (!strcmp(cmdstr, "remove-archive")) {
+ cmd = CMD_REMOVE_ARCHIVE;
+ }
+
+ switch (cmd) {
+ case CMD_LIST_APPS:
+ case CMD_LIST_ARCHIVES:
+ break;
+ case CMD_INSTALL:
+ case CMD_UPGRADE:
+ if (argc < 2) {
+ fprintf(stderr, "ERROR: Missing filename for '%s' command.\n\n", cmdstr);
+ print_usage(argc+optind, argv-optind, 1);
+ exit(2);
+ }
+ cmdarg = argv[1];
+ break;
+ case CMD_UNINSTALL:
+ case CMD_ARCHIVE:
+ case CMD_RESTORE:
+ case CMD_REMOVE_ARCHIVE:
+ if (argc < 2) {
+ fprintf(stderr, "ERROR: Missing bundle ID for '%s' command.\n\n", cmdstr);
+ print_usage(argc+optind, argv-optind, 1);
+ exit(2);
+ }
+ cmdarg = argv[1];
+ break;
+ default:
+ fprintf(stderr, "ERROR: Invalid command '%s'.\n\n", cmdstr);
+ print_usage(argc+optind, argv-optind, 1);
+ exit(2);
+ }
+}
+
+static int afc_upload_file(afc_client_t afc, const char* filename, const char* dstfn)
+{
+ FILE *f = NULL;
+ uint64_t af = 0;
+ char buf[1048576];
+
+ f = fopen(filename, "rb");
+ if (!f) {
+ fprintf(stderr, "fopen: %s: %s\n", filename, strerror(errno));
+ return -1;
+ }
+
+ if ((afc_file_open(afc, dstfn, AFC_FOPEN_WRONLY, &af) != AFC_E_SUCCESS) || !af) {
+ fclose(f);
+ fprintf(stderr, "afc_file_open on '%s' failed!\n", dstfn);
+ return -1;
+ }
+
+ 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;
+ afc_error_t aerr = afc_file_write(afc, af, buf, amount, &written);
+ if (aerr != AFC_E_SUCCESS) {
+ fprintf(stderr, "AFC Write error: %d\n", aerr);
+ break;
+ }
+ total += written;
+ }
+ if (total != amount) {
+ fprintf(stderr, "Error: wrote only %u of %u\n", total, (uint32_t)amount);
+ afc_file_close(afc, af);
+ fclose(f);
+ return -1;
+ }
+ }
+ } while (amount > 0);
+
+ afc_file_close(afc, af);
+ fclose(f);
+
+ return 0;
+}
+
+static void afc_upload_dir(afc_client_t afc, const char* path, const char* afcpath)
+{
+ afc_make_directory(afc, afcpath);
+
+ DIR *dir = opendir(path);
+ if (dir) {
+ struct dirent* ep;
+ while ((ep = readdir(dir))) {
+ if ((strcmp(ep->d_name, ".") == 0) || (strcmp(ep->d_name, "..") == 0)) {
+ continue;
+ }
+ char *fpath = (char*)malloc(strlen(path)+1+strlen(ep->d_name)+1);
+ char *apath = (char*)malloc(strlen(afcpath)+1+strlen(ep->d_name)+1);
+
+ struct stat st;
+
+ strcpy(fpath, path);
+ strcat(fpath, "/");
+ strcat(fpath, ep->d_name);
+
+ strcpy(apath, afcpath);
+ strcat(apath, "/");
+ strcat(apath, ep->d_name);
+
+#ifdef HAVE_LSTAT
+ if ((lstat(fpath, &st) == 0) && S_ISLNK(st.st_mode)) {
+ char *target = (char *)malloc(st.st_size+1);
+ if (readlink(fpath, target, st.st_size+1) < 0) {
+ fprintf(stderr, "ERROR: readlink: %s (%d)\n", strerror(errno), errno);
+ } else {
+ target[st.st_size] = '\0';
+ afc_make_link(afc, AFC_SYMLINK, target, fpath);
+ }
+ free(target);
+ } else
+#endif
+ if ((stat(fpath, &st) == 0) && S_ISDIR(st.st_mode)) {
+ afc_upload_dir(afc, fpath, apath);
+ } else {
+ afc_upload_file(afc, fpath, apath);
+ }
+ free(fpath);
+ free(apath);
+ }
+ closedir(dir);
+ }
+}
+
+static char *buf_from_file(const char *filename, size_t *size)
+{
+ struct stat st;
+ FILE *fp = NULL;
+
+ if (stat(filename, &st) == -1 || (fp = fopen(filename, "r")) == NULL) {
+ return NULL;
+ }
+ size_t filesize = st.st_size;
+ if (filesize == 0) {
+ return NULL;
+ }
+ char *ibuf = malloc(filesize * sizeof(char));
+ if (ibuf == NULL) {
+ return NULL;
+ }
+ size_t amount = fread(ibuf, 1, filesize, fp);
+ if (amount != filesize) {
+ fprintf(stderr, "ERROR: could not read %" PRIu64 " bytes from %s\n", (uint64_t)filesize, filename);
+ free(ibuf);
+ return NULL;
+ }
+ fclose(fp);
+
+ if (size) {
+ *size = filesize;
+ }
+
+ return ibuf;
}
int main(int argc, char **argv)
{
- idevice_t phone = NULL;
+ idevice_t device = NULL;
lockdownd_client_t client = NULL;
instproxy_client_t ipc = NULL;
+ instproxy_error_t err;
np_client_t np = NULL;
afc_client_t afc = NULL;
- uint16_t port = 0;
- int res = 0;
+ lockdownd_service_descriptor_t service = NULL;
+ int res = EXIT_FAILURE;
+ char *bundleidentifier = NULL;
+#ifndef WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
parse_opts(argc, argv);
argc -= optind;
argv += optind;
- if (IDEVICE_E_SUCCESS != idevice_new(&phone, uuid)) {
- fprintf(stderr, "No iPhone found, is it plugged in?\n");
- return -1;
+ if (IDEVICE_E_SUCCESS != idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX)) {
+ if (udid) {
+ fprintf(stderr, "No device found with udid %s.\n", udid);
+ } else {
+ fprintf(stderr, "No device found.\n");
+ }
+ return EXIT_FAILURE;
}
- if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "ideviceinstaller")) {
- fprintf(stderr, "Could not connect to lockdownd. Exiting.\n");
- goto leave_cleanup;
+ if (!udid) {
+ idevice_get_udid(device, &udid);
}
- if ((lockdownd_start_service
- (client, "com.apple.mobile.notification_proxy",
- &port) != LOCKDOWN_E_SUCCESS) || !port) {
- fprintf(stderr,
- "Could not start com.apple.mobile.notification_proxy!\n");
+ lockdownd_error_t lerr = lockdownd_client_new_with_handshake(device, &client, "ideviceinstaller");
+ if (lerr != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "Could not connect to lockdownd: %s. Exiting.\n", lockdownd_strerror(lerr));
goto leave_cleanup;
}
- if (np_client_new(phone, port, &np) != NP_E_SUCCESS) {
- fprintf(stderr, "Could not connect to notification_proxy!\n");
- goto leave_cleanup;
- }
+ if (use_notifier) {
+ lerr =lockdownd_start_service(client, "com.apple.mobile.notification_proxy", &service);
+ if (lerr != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "Could not start com.apple.mobile.notification_proxy: %s\n", lockdownd_strerror(lerr));
+ goto leave_cleanup;
+ }
-#ifdef HAVE_LIBIMOBILEDEVICE_1_0
- np_set_notify_callback(np, notifier, NULL);
-#else
- np_set_notify_callback(np, notifier);
-#endif
+ np_error_t nperr = np_client_new(device, service, &np);
- const char *noties[3] = { NP_APP_INSTALLED, NP_APP_UNINSTALLED, NULL };
+ if (service) {
+ lockdownd_service_descriptor_free(service);
+ }
+ service = NULL;
- np_observe_notifications(np, noties);
+ if (nperr != NP_E_SUCCESS) {
+ fprintf(stderr, "Could not connect to notification_proxy!\n");
+ goto leave_cleanup;
+ }
+
+ np_set_notify_callback(np, notifier, NULL);
+
+ const char *noties[3] = { NP_APP_INSTALLED, NP_APP_UNINSTALLED, NULL };
+
+ np_observe_notifications(np, noties);
+ }
run_again:
- port = 0;
- if ((lockdownd_start_service
- (client, "com.apple.mobile.installation_proxy",
- &port) != LOCKDOWN_E_SUCCESS) || !port) {
- fprintf(stderr,
- "Could not start com.apple.mobile.installation_proxy!\n");
+ if (service) {
+ lockdownd_service_descriptor_free(service);
+ }
+ service = NULL;
+
+ lerr = lockdownd_start_service(client, "com.apple.mobile.installation_proxy", &service);
+ if (lerr != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "Could not start com.apple.mobile.installation_proxy: %s\n", lockdownd_strerror(lerr));
goto leave_cleanup;
}
- if (instproxy_client_new(phone, port, &ipc) != INSTPROXY_E_SUCCESS) {
+ err = instproxy_client_new(device, service, &ipc);
+
+ if (service) {
+ lockdownd_service_descriptor_free(service);
+ }
+ service = NULL;
+
+ if (err != INSTPROXY_E_SUCCESS) {
fprintf(stderr, "Could not connect to installation_proxy!\n");
goto leave_cleanup;
}
setbuf(stdout, NULL);
- if (last_status) {
- free(last_status);
- last_status = NULL;
- }
+ free(last_status);
+ last_status = NULL;
+
notification_expected = 0;
- if (list_apps_mode) {
- int xml_mode = 0;
+ if (cmd == CMD_LIST_APPS) {
plist_t client_opts = instproxy_client_options_new();
instproxy_client_options_add(client_opts, "ApplicationType", "User", NULL);
- instproxy_error_t err;
plist_t apps = NULL;
- /* look for options */
- if (options) {
- char *opts = strdup(options);
- char *elem = strtok(opts, ",");
- while (elem) {
- if (!strcmp(elem, "list_system")) {
- if (!client_opts) {
- client_opts = instproxy_client_options_new();
- }
- instproxy_client_options_add(client_opts, "ApplicationType", "System", NULL);
- } else if (!strcmp(elem, "list_all")) {
- instproxy_client_options_free(client_opts);
- client_opts = NULL;
- } else if (!strcmp(elem, "list_user")) {
- /* do nothing, we're already set */
- } else if (!strcmp(elem, "xml")) {
- xml_mode = 1;
- }
- elem = strtok(NULL, ",");
- }
+ if (opt_list_system && opt_list_user) {
+ plist_dict_remove_item(client_opts, "ApplicationType");
+ } else if (opt_list_system) {
+ instproxy_client_options_add(client_opts, "ApplicationType", "System", NULL);
+ } else if (opt_list_user) {
+ instproxy_client_options_add(client_opts, "ApplicationType", "User", NULL);
}
- err = instproxy_browse(ipc, client_opts, &apps);
- instproxy_client_options_free(client_opts);
- if (err != INSTPROXY_E_SUCCESS) {
- fprintf(stderr, "ERROR: instproxy_browse returned %d\n", err);
- goto leave_cleanup;
+ if (bundle_ids) {
+ plist_dict_set_item(client_opts, "BundleIDs", plist_copy(bundle_ids));
}
- if (!apps || (plist_get_node_type(apps) != PLIST_ARRAY)) {
- fprintf(stderr,
- "ERROR: instproxy_browse returnd an invalid plist!\n");
- goto leave_cleanup;
+
+ if (!output_format && !return_attrs) {
+ return_attrs = plist_new_array();
+ plist_array_append_item(return_attrs, plist_new_string("CFBundleIdentifier"));
+ plist_array_append_item(return_attrs, plist_new_string("CFBundleShortVersionString"));
+ plist_array_append_item(return_attrs, plist_new_string("CFBundleDisplayName"));
}
- if (xml_mode) {
- char *xml = NULL;
- uint32_t len = 0;
- plist_to_xml(apps, &xml, &len);
- if (xml) {
- puts(xml);
- free(xml);
+ if (return_attrs) {
+ instproxy_client_options_add(client_opts, "ReturnAttributes", return_attrs, NULL);
+ }
+
+ if (output_format) {
+ err = instproxy_browse(ipc, client_opts, &apps);
+
+ if (!apps || (plist_get_node_type(apps) != PLIST_ARRAY)) {
+ fprintf(stderr, "ERROR: instproxy_browse returnd an invalid plist!\n");
+ goto leave_cleanup;
+ }
+ char *buf = NULL;
+ uint32_t len = 0;
+ if (output_format == FORMAT_XML) {
+ plist_err_t perr = plist_to_xml(apps, &buf, &len);
+ if (perr != PLIST_ERR_SUCCESS) {
+ fprintf(stderr, "ERROR: Failed to convert data to XML format (%d).\n", perr);
+ }
+ } else if (output_format == FORMAT_JSON) {
+ /* for JSON, we need to convert some stuff since it doesn't support PLIST_DATA nodes */
+ plist_array_iter aiter = NULL;
+ plist_array_new_iter(apps, &aiter);
+ plist_t entry = NULL;
+ do {
+ plist_array_next_item(apps, aiter, &entry);
+ if (!entry) break;
+ plist_t items = plist_dict_get_item(entry, "UIApplicationShortcutItems");
+ plist_array_iter inner = NULL;
+ plist_array_new_iter(items, &inner);
+ plist_t item = NULL;
+ do {
+ plist_array_next_item(items, inner, &item);
+ if (!item) break;
+ plist_t userinfo = plist_dict_get_item(item, "UIApplicationShortcutItemUserInfo");
+ if (userinfo) {
+ plist_t data_node = plist_dict_get_item(userinfo, "data");
+
+ if (data_node) {
+ char *strbuf = NULL;
+ uint32_t buflen = 0;
+ plist_write_to_string(data_node, &strbuf, &buflen, PLIST_FORMAT_LIMD, PLIST_OPT_NO_NEWLINE);
+ plist_set_string_val(data_node, strbuf);
+ free(strbuf);
+ }
+ }
+ } while (item);
+ free(inner);
+ } while (entry);
+ free(aiter);
+ plist_err_t perr = plist_to_json(apps, &buf, &len, 1);
+ if (perr != PLIST_ERR_SUCCESS) {
+ fprintf(stderr, "ERROR: Failed to convert data to JSON format (%d).\n", perr);
+ }
+ }
+ if (buf) {
+ puts(buf);
+ free(buf);
}
plist_free(apps);
+ res = 0;
goto leave_cleanup;
}
- printf("Total: %d apps\n", plist_array_get_size(apps));
- uint32_t i = 0;
- for (i = 0; i < plist_array_get_size(apps); i++) {
- plist_t app = plist_array_get_item(apps, i);
- plist_t p_appid =
- plist_dict_get_item(app, "CFBundleIdentifier");
- char *s_appid = NULL;
- char *s_dispName = NULL;
- char *s_version = NULL;
- plist_t dispName =
- plist_dict_get_item(app, "CFBundleDisplayName");
- plist_t version = plist_dict_get_item(app, "CFBundleVersion");
-
- if (p_appid) {
- plist_get_string_val(p_appid, &s_appid);
- }
- if (!s_appid) {
- fprintf(stderr, "ERROR: Failed to get APPID!\n");
- break;
- }
- if (dispName) {
- plist_get_string_val(dispName, &s_dispName);
- }
- if (version) {
- plist_get_string_val(version, &s_version);
- }
+ print_apps_header();
- if (!s_dispName) {
- s_dispName = strdup(s_appid);
- }
- if (s_version) {
- printf("%s - %s %s\n", s_appid, s_dispName, s_version);
- free(s_version);
- } else {
- printf("%s - %s\n", s_appid, s_dispName);
- }
- free(s_dispName);
- free(s_appid);
+ err = instproxy_browse_with_callback(ipc, client_opts, status_cb, NULL);
+ if (err == INSTPROXY_E_RECEIVE_TIMEOUT) {
+ fprintf(stderr, "NOTE: timeout waiting for device to browse apps, trying again...\n");
}
- plist_free(apps);
- } else if (install_mode || upgrade_mode) {
+
+ instproxy_client_options_free(client_opts);
+ if (err != INSTPROXY_E_SUCCESS) {
+ fprintf(stderr, "ERROR: instproxy_browse returned %d\n", err);
+ goto leave_cleanup;
+ }
+
+ wait_for_command_complete = 1;
+ notification_expected = 0;
+ } else if (cmd == CMD_INSTALL || cmd == CMD_UPGRADE) {
plist_t sinf = NULL;
plist_t meta = NULL;
char *pkgname = NULL;
struct stat fst;
- FILE *f = NULL;
uint64_t af = 0;
char buf[8192];
- port = 0;
- if ((lockdownd_start_service(client, "com.apple.afc", &port) !=
- LOCKDOWN_E_SUCCESS) || !port) {
- fprintf(stderr, "Could not start com.apple.afc!\n");
+ lockdownd_service_descriptor_free(service);
+ service = NULL;
+
+ lerr = lockdownd_start_service(client, "com.apple.afc", &service);
+ if (lerr != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "Could not start com.apple.afc: %s\n", lockdownd_strerror(lerr));
goto leave_cleanup;
}
lockdownd_client_free(client);
client = NULL;
- if (afc_client_new(phone, port, &afc) != INSTPROXY_E_SUCCESS) {
+ if (afc_client_new(device, service, &afc) != AFC_E_SUCCESS) {
fprintf(stderr, "Could not connect to AFC!\n");
goto leave_cleanup;
}
- if (stat(appid, &fst) != 0) {
- fprintf(stderr, "ERROR: stat: %s: %s\n", appid, strerror(errno));
+ if (stat(cmdarg, &fst) != 0) {
+ fprintf(stderr, "ERROR: stat: %s: %s\n", cmdarg, strerror(errno));
goto leave_cleanup;
}
+ 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);
+ }
+
+ plist_t client_opts = instproxy_client_options_new();
+
/* open install package */
int errp = 0;
- struct zip *zf = zip_open(appid, 0, &errp);
- if (!zf) {
- fprintf(stderr, "ERROR: zip_open: %s: %d\n", appid, errp);
- goto leave_cleanup;
- }
+ struct zip *zf = NULL;
- /* extract iTunesMetadata.plist from package */
- char *zbuf = NULL;
- uint32_t len = 0;
- if (zip_f_get_contents(zf, "iTunesMetadata.plist", 0, &zbuf, &len) == 0) {
- meta = plist_new_data(zbuf, len);
- }
- if (zbuf) {
- free(zbuf);
- }
+ if ((strlen(cmdarg) > 5) && (strcmp(&cmdarg[strlen(cmdarg)-5], ".ipcc") == 0)) {
+ zf = zip_open(cmdarg, 0, &errp);
+ if (!zf) {
+ fprintf(stderr, "ERROR: zip_open: %s: %d\n", cmdarg, errp);
+ goto leave_cleanup;
+ }
- /* we need to get the CFBundleName first */
- plist_t info = NULL;
- zbuf = NULL;
- len = 0;
- if (zip_f_get_contents(zf, "Info.plist", ZIP_FL_NODIR, &zbuf, &len) < 0) {
- zip_unchange_all(zf);
- zip_close(zf);
- goto leave_cleanup;
- }
- if (memcmp(zbuf, "bplist00", 8) == 0) {
- plist_from_bin(zbuf, len, &info);
- } else {
- plist_from_xml(zbuf, len, &info);
- }
- free(zbuf);
+ char* ipcc = strdup(cmdarg);
+ if ((asprintf(&pkgname, "%s/%s", PKG_PATH, basename(ipcc)) > 0) && pkgname) {
+ afc_make_directory(afc, pkgname);
+ }
- if (!info) {
- fprintf(stderr, "Could not parse Info.plist!\n");
- zip_unchange_all(zf);
- zip_close(zf);
- goto leave_cleanup;
- }
+ printf("Uploading %s package contents... ", basename(ipcc));
+
+ /* extract the contents of the .ipcc file to PublicStaging/<name>.ipcc directory */
+ zip_int64_t numzf = (zip_int64_t)zip_get_num_entries(zf, 0);
+ zip_int64_t i = 0;
+ for (i = 0; numzf > 0 && i < numzf; i++) {
+ const char* zname = zip_get_name(zf, i, 0);
+ char* dstpath = NULL;
+ if (!zname) continue;
+ if (zname[strlen(zname)-1] == '/') {
+ // directory
+ if ((asprintf(&dstpath, "%s/%s/%s", PKG_PATH, basename(ipcc), zname) > 0) && dstpath) {
+ afc_make_directory(afc, dstpath); }
+ free(dstpath);
+ dstpath = NULL;
+ } else {
+ // file
+ struct zip_file* zfile = zip_fopen_index(zf, i, 0);
+ if (!zfile) continue;
+
+ if ((asprintf(&dstpath, "%s/%s/%s", PKG_PATH, basename(ipcc), zname) <= 0) || !dstpath || (afc_file_open(afc, dstpath, AFC_FOPEN_WRONLY, &af) != AFC_E_SUCCESS)) {
+ fprintf(stderr, "ERROR: can't open afc://%s for writing\n", dstpath);
+ free(dstpath);
+ dstpath = NULL;
+ zip_fclose(zfile);
+ continue;
+ }
- char *bundlename = NULL;
+ struct zip_stat zs;
+ zip_stat_init(&zs);
+ if (zip_stat_index(zf, i, 0, &zs) != 0) {
+ fprintf(stderr, "ERROR: zip_stat_index %" PRIu64 " failed!\n", i);
+ free(dstpath);
+ dstpath = NULL;
+ zip_fclose(zfile);
+ continue;
+ }
- plist_t bname = plist_dict_get_item(info, "CFBundleName");
- if (bname) {
- plist_get_string_val(bname, &bundlename);
- }
- plist_free(info);
+ free(dstpath);
+ dstpath = NULL;
+
+ zip_uint64_t zfsize = 0;
+ while (zfsize < zs.size) {
+ zip_int64_t amount = zip_fread(zfile, buf, sizeof(buf));
+ if (amount == 0) {
+ break;
+ }
+
+ 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;
+ }
+ total += written;
+ }
+ if (total != amount) {
+ fprintf(stderr, "Error: wrote only %d of %" PRIi64 "\n", total, amount);
+ afc_file_close(afc, af);
+ zip_fclose(zfile);
+ free(dstpath);
+ goto leave_cleanup;
+ }
+ }
+
+ zfsize += amount;
+ }
- if (!bundlename) {
- fprintf(stderr, "Could not determine CFBundleName!\n");
- zip_unchange_all(zf);
- zip_close(zf);
- goto leave_cleanup;
- }
+ afc_file_close(afc, af);
+ af = 0;
- char *sinfname = NULL;
- if (asprintf(&sinfname, "Payload/%s.app/SC_Info/%s.sinf", bundlename, bundlename) < 0) {
- fprintf(stderr, "Out of memory!?\n");
- goto leave_cleanup;
- }
- free(bundlename);
+ zip_fclose(zfile);
+ }
+ }
+ free(ipcc);
+ printf("DONE.\n");
- /* extract .sinf from package */
- zbuf = NULL;
- len = 0;
- if (zip_f_get_contents(zf, sinfname, 0, &zbuf, &len) == 0) {
- sinf = plist_new_data(zbuf, len);
- }
- free(sinfname);
- if (zbuf) {
- free(zbuf);
- }
+ instproxy_client_options_add(client_opts, "PackageType", "CarrierBundle", NULL);
+ } else if (S_ISDIR(fst.st_mode)) {
+ /* upload developer app directory */
+ instproxy_client_options_add(client_opts, "PackageType", "Developer", NULL);
- zip_unchange_all(zf);
- zip_close(zf);
+ if (asprintf(&pkgname, "%s/%s", PKG_PATH, basename(cmdarg)) < 0) {
+ fprintf(stderr, "ERROR: Out of memory allocating pkgname!?\n");
+ goto leave_cleanup;
+ }
- /* copy archive to device */
- f = fopen(appid, "r");
- if (!f) {
- fprintf(stderr, "fopen: %s: %s\n", appid, strerror(errno));
- goto leave_cleanup;
- }
+ printf("Uploading %s package contents... ", basename(cmdarg));
+ afc_upload_dir(afc, cmdarg, pkgname);
+ printf("DONE.\n");
- pkgname = NULL;
- if (asprintf(&pkgname, "%s/%s", PKG_PATH, basename(appid)) < 0) {
- fprintf(stderr, "Out of memory!?\n");
- goto leave_cleanup;
- }
+ /* extract the CFBundleIdentifier from the package */
- printf("Copying '%s' --> '%s'\n", appid, pkgname);
+ /* construct full filename to Info.plist */
+ char *filename = (char*)malloc(strlen(cmdarg)+11+1);
+ strcpy(filename, cmdarg);
+ strcat(filename, "/Info.plist");
- 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);
+ struct stat st;
+ FILE *fp = NULL;
+
+ if (stat(filename, &st) == -1 || (fp = fopen(filename, "r")) == NULL) {
+ fprintf(stderr, "ERROR: could not locate %s in app!\n", filename);
+ free(filename);
+ goto leave_cleanup;
}
- }
- if (strs) {
- int i = 0;
- while (strs[i]) {
- free(strs[i]);
- i++;
+ size_t filesize = st.st_size;
+ char *ibuf = malloc(filesize * sizeof(char));
+ size_t amount = fread(ibuf, 1, filesize, fp);
+ if (amount != filesize) {
+ fprintf(stderr, "ERROR: could not read %u bytes from %s\n", (uint32_t)filesize, filename);
+ free(filename);
+ goto leave_cleanup;
}
- free(strs);
- }
+ fclose(fp);
+ free(filename);
- if ((afc_file_open(afc, pkgname, AFC_FOPEN_WRONLY, &af) !=
- AFC_E_SUCCESS) || !af) {
- fclose(f);
- fprintf(stderr, "afc_file_open on '%s' failed!\n", pkgname);
- free(pkgname);
- goto leave_cleanup;
- }
+ plist_t info = NULL;
+ plist_from_memory(ibuf, filesize, &info, NULL);
+ free(ibuf);
- 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;
- }
- total += written;
+ if (!info) {
+ fprintf(stderr, "ERROR: could not parse Info.plist!\n");
+ goto leave_cleanup;
+ }
+
+ plist_t bname = plist_dict_get_item(info, "CFBundleIdentifier");
+ if (bname) {
+ plist_get_string_val(bname, &bundleidentifier);
+ }
+ plist_free(info);
+ info = NULL;
+ } else {
+ zf = zip_open(cmdarg, 0, &errp);
+ if (!zf) {
+ fprintf(stderr, "ERROR: zip_open: %s: %d\n", cmdarg, errp);
+ goto leave_cleanup;
+ }
+
+ char *zbuf = NULL;
+ uint32_t len = 0;
+ plist_t meta_dict = NULL;
+
+ if (extmeta) {
+ size_t flen = 0;
+ zbuf = buf_from_file(extmeta, &flen);
+ if (zbuf && flen) {
+ meta = plist_new_data(zbuf, flen);
+ plist_from_memory(zbuf, flen, &meta_dict, NULL);
+ free(zbuf);
}
- if (total != amount) {
- fprintf(stderr, "Error: wrote only %d of %zu\n", total,
- amount);
- afc_file_close(afc, af);
- fclose(f);
- free(pkgname);
+ if (!meta_dict) {
+ plist_free(meta);
+ meta = NULL;
+ fprintf(stderr, "WARNING: could not load external iTunesMetadata %s!\n", extmeta);
+ }
+ zbuf = NULL;
+ }
+
+ if (!meta && !meta_dict) {
+ /* extract iTunesMetadata.plist from package */
+ if (zip_get_contents(zf, ITUNES_METADATA_PLIST_FILENAME, 0, &zbuf, &len) == 0) {
+ meta = plist_new_data(zbuf, len);
+ plist_from_memory(zbuf, len, &meta_dict, NULL);
+ }
+ if (!meta_dict) {
+ plist_free(meta);
+ meta = NULL;
+ fprintf(stderr, "WARNING: could not locate %s in archive!\n", ITUNES_METADATA_PLIST_FILENAME);
+ }
+ free(zbuf);
+ }
+
+ /* determine .app directory in archive */
+ zbuf = NULL;
+ len = 0;
+ plist_t info = NULL;
+ char* filename = NULL;
+ char* app_directory_name = NULL;
+
+ if (zip_get_app_directory(zf, &app_directory_name)) {
+ fprintf(stderr, "ERROR: Unable to locate .app directory in archive. Make sure it is inside a 'Payload' directory.\n");
+ goto leave_cleanup;
+ }
+
+ /* construct full filename to Info.plist */
+ filename = (char*)malloc(strlen(app_directory_name)+10+1);
+ strcpy(filename, app_directory_name);
+ free(app_directory_name);
+ app_directory_name = NULL;
+ strcat(filename, "Info.plist");
+
+ if (zip_get_contents(zf, filename, 0, &zbuf, &len) < 0) {
+ fprintf(stderr, "WARNING: could not locate %s in archive!\n", filename);
+ free(filename);
+ zip_unchange_all(zf);
+ zip_close(zf);
+ goto leave_cleanup;
+ }
+ free(filename);
+ plist_from_memory(zbuf, len, &info, NULL);
+ free(zbuf);
+
+ if (!info) {
+ fprintf(stderr, "Could not parse Info.plist!\n");
+ zip_unchange_all(zf);
+ zip_close(zf);
+ goto leave_cleanup;
+ }
+
+ char *bundleexecutable = NULL;
+
+ plist_t bname = plist_dict_get_item(info, "CFBundleExecutable");
+ if (bname) {
+ plist_get_string_val(bname, &bundleexecutable);
+ }
+
+ bname = plist_dict_get_item(info, "CFBundleIdentifier");
+ if (bname) {
+ plist_get_string_val(bname, &bundleidentifier);
+ }
+ plist_free(info);
+ info = NULL;
+
+ if (!bundleexecutable) {
+ fprintf(stderr, "Could not determine value for CFBundleExecutable!\n");
+ zip_unchange_all(zf);
+ zip_close(zf);
+ goto leave_cleanup;
+ }
+
+ if (extsinf) {
+ size_t flen = 0;
+ zbuf = buf_from_file(extsinf, &flen);
+ if (zbuf && flen) {
+ sinf = plist_new_data(zbuf, flen);
+ free(zbuf);
+ } else {
+ fprintf(stderr, "WARNING: could not load external SINF %s!\n", extsinf);
+ }
+ zbuf = NULL;
+ }
+
+ if (!sinf) {
+ char *sinfname = NULL;
+ if (asprintf(&sinfname, "Payload/%s.app/SC_Info/%s.sinf", bundleexecutable, bundleexecutable) < 0) {
+ fprintf(stderr, "Out of memory!?\n");
goto leave_cleanup;
}
+ free(bundleexecutable);
+
+ /* extract .sinf from package */
+ zbuf = NULL;
+ len = 0;
+ if (zip_get_contents(zf, sinfname, 0, &zbuf, &len) == 0) {
+ sinf = plist_new_data(zbuf, len);
+ } else {
+ fprintf(stderr, "WARNING: could not locate %s in archive!\n", sinfname);
+ }
+ free(sinfname);
+ free(zbuf);
}
- }
- while (amount > 0);
- afc_file_close(afc, af);
- fclose(f);
+ /* copy archive to device */
+ pkgname = NULL;
+ if (asprintf(&pkgname, "%s/%s", PKG_PATH, bundleidentifier) < 0) {
+ fprintf(stderr, "Out of memory!?\n");
+ goto leave_cleanup;
+ }
- printf("done.\n");
+ printf("Copying '%s' to device... ", cmdarg);
- /* perform installation or upgrade */
- plist_t client_opts = instproxy_client_options_new();
- if (sinf) {
- instproxy_client_options_add(client_opts, "ApplicationSINF", sinf, NULL);
+ if (afc_upload_file(afc, cmdarg, pkgname) < 0) {
+ printf("FAILED\n");
+ free(pkgname);
+ goto leave_cleanup;
+ }
+
+ printf("DONE.\n");
+
+ if (bundleidentifier) {
+ instproxy_client_options_add(client_opts, "CFBundleIdentifier", bundleidentifier, NULL);
+ }
+ if (sinf) {
+ instproxy_client_options_add(client_opts, "ApplicationSINF", sinf, NULL);
+ }
+ if (meta) {
+ instproxy_client_options_add(client_opts, "iTunesMetadata", meta, NULL);
+ }
}
- if (meta) {
- instproxy_client_options_add(client_opts, "iTunesMetadata", meta, NULL);
+ if (zf) {
+ zip_unchange_all(zf);
+ zip_close(zf);
}
- if (install_mode) {
- printf("Installing '%s'\n", pkgname);
-#ifdef HAVE_LIBIMOBILEDEVICE_1_1
+
+ /* perform installation or upgrade */
+ if (cmd == CMD_INSTALL) {
+ printf("Installing '%s'\n", bundleidentifier);
instproxy_install(ipc, pkgname, client_opts, status_cb, NULL);
-#else
- instproxy_install(ipc, pkgname, client_opts, status_cb);
-#endif
} else {
- printf("Upgrading '%s'\n", pkgname);
-#ifdef HAVE_LIBIMOBILEDEVICE_1_1
+ printf("Upgrading '%s'\n", bundleidentifier);
instproxy_upgrade(ipc, pkgname, client_opts, status_cb, NULL);
-#else
- instproxy_upgrade(ipc, pkgname, client_opts, status_cb);
-#endif
}
instproxy_client_options_free(client_opts);
free(pkgname);
- wait_for_op_complete = 1;
- notification_expected = 1;
- } else if (uninstall_mode) {
-#ifdef HAVE_LIBIMOBILEDEVICE_1_1
- instproxy_uninstall(ipc, appid, NULL, status_cb, NULL);
-#else
- instproxy_uninstall(ipc, appid, NULL, status_cb);
-#endif
- wait_for_op_complete = 1;
+ wait_for_command_complete = 1;
notification_expected = 1;
- } else if (list_archives_mode) {
- int xml_mode = 0;
+ } else if (cmd == CMD_UNINSTALL) {
+ printf("Uninstalling '%s'\n", cmdarg);
+ instproxy_uninstall(ipc, cmdarg, NULL, status_cb, NULL);
+ wait_for_command_complete = 1;
+ notification_expected = 0;
+ } else if (cmd == CMD_LIST_ARCHIVES) {
plist_t dict = NULL;
- plist_t lres = NULL;
- instproxy_error_t err;
-
- /* look for options */
- if (options) {
- char *opts = strdup(options);
- char *elem = strtok(opts, ",");
- while (elem) {
- if (!strcmp(elem, "xml")) {
- xml_mode = 1;
- }
- elem = strtok(NULL, ",");
- }
- }
err = instproxy_lookup_archives(ipc, NULL, &dict);
if (err != INSTPROXY_E_SUCCESS) {
fprintf(stderr, "ERROR: lookup_archives returned %d\n", err);
goto leave_cleanup;
}
- if (!dict) {
- fprintf(stderr,
- "ERROR: lookup_archives did not return a plist!?\n");
- goto leave_cleanup;
- }
- lres = plist_dict_get_item(dict, "LookupResult");
- if (!lres || (plist_get_node_type(lres) != PLIST_DICT)) {
- plist_free(dict);
- fprintf(stderr, "ERROR: Could not get dict 'LookupResult'\n");
+ if (!dict) {
+ fprintf(stderr, "ERROR: lookup_archives did not return a plist!?\n");
goto leave_cleanup;
}
- if (xml_mode) {
- char *xml = NULL;
+ if (output_format) {
+ char *buf = NULL;
uint32_t len = 0;
-
- plist_to_xml(lres, &xml, &len);
- if (xml) {
- puts(xml);
- free(xml);
+ if (output_format == FORMAT_XML) {
+ plist_err_t perr = plist_to_xml(dict, &buf, &len);
+ if (perr != PLIST_ERR_SUCCESS) {
+ fprintf(stderr, "ERROR: Failed to convert data to XML format (%d).\n", perr);
+ }
+ } else if (output_format == FORMAT_JSON) {
+ plist_err_t perr = plist_to_json(dict, &buf, &len, 1);
+ if (perr != PLIST_ERR_SUCCESS) {
+ fprintf(stderr, "ERROR: Failed to convert data to JSON format (%d).\n", perr);
+ }
+ }
+ if (buf) {
+ puts(buf);
+ free(buf);
}
plist_free(dict);
goto leave_cleanup;
@@ -749,8 +1400,8 @@ run_again:
plist_t node = NULL;
char *key = NULL;
- printf("Total: %d archived apps\n", plist_dict_get_size(lres));
- plist_dict_new_iter(lres, &iter);
+ printf("Total: %d archived apps\n", plist_dict_get_size(dict));
+ plist_dict_new_iter(dict, &iter);
if (!iter) {
plist_free(dict);
fprintf(stderr, "ERROR: Could not create plist_dict_iter!\n");
@@ -759,14 +1410,14 @@ run_again:
do {
key = NULL;
node = NULL;
- plist_dict_next_item(lres, iter, &key, &node);
+ plist_dict_next_item(dict, iter, &key, &node);
if (key && (plist_get_node_type(node) == PLIST_DICT)) {
char *s_dispName = NULL;
char *s_version = NULL;
plist_t dispName =
plist_dict_get_item(node, "CFBundleDisplayName");
plist_t version =
- plist_dict_get_item(node, "CFBundleVersion");
+ plist_dict_get_item(node, "CFBundleShortVersionString");
if (dispName) {
plist_get_string_val(dispName, &s_dispName);
}
@@ -788,38 +1439,18 @@ run_again:
}
while (node);
plist_free(dict);
- } else if (archive_mode) {
- char *copy_path = NULL;
- int remove_after_copy = 0;
- int skip_uninstall = 1;
- int app_only = 0;
+ } else if (cmd == CMD_ARCHIVE) {
plist_t client_opts = NULL;
- /* look for options */
- if (options) {
- char *opts = strdup(options);
- char *elem = strtok(opts, ",");
- while (elem) {
- if (!strcmp(elem, "uninstall")) {
- skip_uninstall = 0;
- } else if (!strcmp(elem, "app_only")) {
- app_only = 1;
- } else if ((strlen(elem) > 5) && !strncmp(elem, "copy=", 5)) {
- copy_path = strdup(elem+5);
- } else if (!strcmp(elem, "remove")) {
- remove_after_copy = 1;
- }
- elem = strtok(NULL, ",");
- }
- }
-
- if (skip_uninstall || app_only) {
+ if (skip_uninstall || app_only || docs_only) {
client_opts = instproxy_client_options_new();
if (skip_uninstall) {
instproxy_client_options_add(client_opts, "SkipUninstall", 1, NULL);
}
if (app_only) {
instproxy_client_options_add(client_opts, "ArchiveType", "ApplicationOnly", NULL);
+ } else if (docs_only) {
+ instproxy_client_options_add(client_opts, "ArchiveType", "DocumentsOnly", NULL);
}
}
@@ -827,49 +1458,47 @@ run_again:
struct stat fst;
if (stat(copy_path, &fst) != 0) {
fprintf(stderr, "ERROR: stat: %s: %s\n", copy_path, strerror(errno));
- free(copy_path);
goto leave_cleanup;
}
if (!S_ISDIR(fst.st_mode)) {
fprintf(stderr, "ERROR: '%s' is not a directory as expected.\n", copy_path);
- free(copy_path);
goto leave_cleanup;
}
- port = 0;
- if ((lockdownd_start_service(client, "com.apple.afc", &port) != LOCKDOWN_E_SUCCESS) || !port) {
+ if (service) {
+ lockdownd_service_descriptor_free(service);
+ }
+ service = NULL;
+
+ if ((lockdownd_start_service(client, "com.apple.afc", &service) != LOCKDOWN_E_SUCCESS) || !service) {
fprintf(stderr, "Could not start com.apple.afc!\n");
- free(copy_path);
goto leave_cleanup;
}
lockdownd_client_free(client);
client = NULL;
- if (afc_client_new(phone, port, &afc) != INSTPROXY_E_SUCCESS) {
+ if (afc_client_new(device, service, &afc) != AFC_E_SUCCESS) {
fprintf(stderr, "Could not connect to AFC!\n");
goto leave_cleanup;
}
}
-#ifdef HAVE_LIBIMOBILEDEVICE_1_1
- instproxy_archive(ipc, appid, client_opts, status_cb, NULL);
-#else
- instproxy_archive(ipc, appid, client_opts, status_cb);
-#endif
+ instproxy_archive(ipc, cmdarg, client_opts, status_cb, NULL);
+
instproxy_client_options_free(client_opts);
- wait_for_op_complete = 1;
+ wait_for_command_complete = 1;
if (skip_uninstall) {
notification_expected = 0;
} else {
notification_expected = 1;
}
- do_wait_when_needed();
+ idevice_wait_for_command_to_complete();
if (copy_path) {
- if (err_occured) {
+ if (err_occurred) {
afc_client_free(afc);
afc = NULL;
goto leave_cleanup;
@@ -878,13 +1507,12 @@ run_again:
uint64_t af = 0;
/* local filename */
char *localfile = NULL;
- if (asprintf(&localfile, "%s/%s.ipa", copy_path, appid) < 0) {
+ if (asprintf(&localfile, "%s/%s.ipa", copy_path, cmdarg) < 0) {
fprintf(stderr, "Out of memory!?\n");
goto leave_cleanup;
}
- free(copy_path);
- f = fopen(localfile, "w");
+ f = fopen(localfile, "wb");
if (!f) {
fprintf(stderr, "ERROR: fopen: %s: %s\n", localfile, strerror(errno));
free(localfile);
@@ -893,7 +1521,7 @@ run_again:
/* remote filename */
char *remotefile = NULL;
- if (asprintf(&remotefile, "%s/%s.zip", APPARCH_PATH, appid) < 0) {
+ if (asprintf(&remotefile, "%s/%s.zip", APPARCH_PATH, cmdarg) < 0) {
fprintf(stderr, "Out of memory!?\n");
goto leave_cleanup;
}
@@ -939,7 +1567,7 @@ run_again:
}
/* copy file over */
- printf("Copying '%s' --> '%s'\n", remotefile, localfile);
+ printf("Copying '%s' --> '%s'... ", remotefile, localfile);
free(remotefile);
free(localfile);
@@ -966,7 +1594,8 @@ run_again:
afc_file_close(afc, af);
fclose(f);
- printf("done.\n");
+ printf("DONE.\n");
+
if (total != fsize) {
fprintf(stderr, "WARNING: remote and local file sizes don't match (%d != %d)\n", fsize, total);
if (remove_after_copy) {
@@ -977,12 +1606,9 @@ run_again:
if (remove_after_copy) {
/* remove archive if requested */
- printf("Removing '%s'\n", appid);
- archive_mode = 0;
- remove_archive_mode = 1;
- free(options);
- options = NULL;
- if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "ideviceinstaller")) {
+ printf("Removing '%s'\n", cmdarg);
+ cmd = CMD_REMOVE_ARCHIVE;
+ if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(device, &client, "ideviceinstaller")) {
fprintf(stderr, "Could not connect to lockdownd. Exiting.\n");
goto leave_cleanup;
}
@@ -990,59 +1616,43 @@ run_again:
}
}
goto leave_cleanup;
- } else if (restore_mode) {
-#ifdef HAVE_LIBIMOBILEDEVICE_1_1
- instproxy_restore(ipc, appid, NULL, status_cb, NULL);
-#else
- instproxy_restore(ipc, appid, NULL, status_cb);
-#endif
- wait_for_op_complete = 1;
+ } else if (cmd == CMD_RESTORE) {
+ instproxy_restore(ipc, cmdarg, NULL, status_cb, NULL);
+ wait_for_command_complete = 1;
notification_expected = 1;
- } else if (remove_archive_mode) {
-#ifdef HAVE_LIBIMOBILEDEVICE_1_1
- instproxy_remove_archive(ipc, appid, NULL, status_cb, NULL);
-#else
- instproxy_remove_archive(ipc, appid, NULL, status_cb);
-#endif
- wait_for_op_complete = 1;
+ } else if (cmd == CMD_REMOVE_ARCHIVE) {
+ instproxy_remove_archive(ipc, cmdarg, NULL, status_cb, NULL);
+ wait_for_command_complete = 1;
} else {
- printf
- ("ERROR: no operation selected?! This should not be reached!\n");
- res = -2;
+ printf("ERROR: no command selected?! This should not be reached!\n");
+ res = 2;
goto leave_cleanup;
}
- if (client) {
- /* not needed anymore */
- lockdownd_client_free(client);
- client = NULL;
- }
-
- do_wait_when_needed();
-
- leave_cleanup:
- if (np) {
- np_client_free(np);
- }
- if (ipc) {
- instproxy_client_free(ipc);
- }
- if (afc) {
- afc_client_free(afc);
- }
- if (client) {
- lockdownd_client_free(client);
- }
- idevice_free(phone);
-
- if (uuid) {
- free(uuid);
- }
- if (appid) {
- free(appid);
- }
- if (options) {
- free(options);
+ /* not needed anymore */
+ lockdownd_client_free(client);
+ client = NULL;
+
+ idevice_wait_for_command_to_complete();
+ res = 0;
+
+leave_cleanup:
+ np_client_free(np);
+ instproxy_client_free(ipc);
+ afc_client_free(afc);
+ lockdownd_client_free(client);
+ idevice_free(device);
+
+ free(udid);
+ free(copy_path);
+ free(extsinf);
+ free(extmeta);
+ free(bundleidentifier);
+ plist_free(bundle_ids);
+ plist_free(return_attrs);
+
+ if (err_occurred && !res) {
+ res = 128;
}
return res;