summaryrefslogtreecommitdiffstats
path: root/src/ideviceinstaller.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ideviceinstaller.c')
-rw-r--r--src/ideviceinstaller.c1009
1 files changed, 1009 insertions, 0 deletions
diff --git a/src/ideviceinstaller.c b/src/ideviceinstaller.c
new file mode 100644
index 0000000..808a1e9
--- /dev/null
+++ b/src/ideviceinstaller.c
@@ -0,0 +1,1009 @@
+/**
+ * ideviceinstaller -- Manage iPhone/iPod apps
+ *
+ * Copyright (C) 2010 Nikias Bassen <nikias@gmx.li>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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 profile.
+ *
+ * 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
+ */
+#include <stdlib.h>
+#define _GNU_SOURCE 1
+#define __USE_GNU 1
+#include <stdio.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <time.h>
+
+#include <libimobiledevice/libimobiledevice.h>
+#include <libimobiledevice/lockdown.h>
+#include <libimobiledevice/installation_proxy.h>
+#include <libimobiledevice/notification_proxy.h>
+#include <libimobiledevice/afc.h>
+
+#include <plist/plist.h>
+
+#include <zip.h>
+
+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 *last_status = NULL;
+int wait_for_op_complete = 0;
+int notification_expected = 0;
+int op_completed = 0;
+int err_occured = 0;
+int notified = 0;
+
+
+static void notifier(const char *notification)
+{
+ /* printf("notification received: %s\n", notification);*/
+ notified = 1;
+}
+
+static void status_cb(const char *operation, plist_t status)
+{
+ 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 (!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);
+ }
+ } 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);
+ }
+ } else {
+ printf("%s: called with invalid data!\n", __func__);
+ }
+}
+
+static int zip_f_get_contents(struct zip *zf, const char *filename, int locate_flags, char **buffer, uint32_t *len)
+{
+ struct zip_stat zs;
+ struct zip_file *zfile;
+ int zindex = zip_name_locate(zf, filename, locate_flags);
+
+ *buffer = NULL;
+ *len = 0;
+
+ if (zindex < 0) {
+ fprintf(stderr, "ERROR: could not locate %s in archive!\n", filename);
+ return -1;
+ }
+
+ zip_stat_init(&zs);
+
+ if (zip_stat_index(zf, zindex, 0, &zs) != 0) {
+ fprintf(stderr, "ERROR: zip_stat_index '%s' failed!\n", filename);
+ return -2;
+ }
+
+ if (zs.size > 10485760) {
+ fprintf(stderr, "ERROR: file '%s' is too large!\n", filename);
+ return -3;
+ }
+
+ zfile = zip_fopen_index(zf, zindex, 0);
+ if (!zfile) {
+ fprintf(stderr, "ERROR: zip_fopen '%s' failed!\n", filename);
+ return -4;
+ }
+
+ *buffer = malloc(zs.size);
+ if (zip_fread(zfile, *buffer, zs.size) != zs.size) {
+ fprintf(stderr, "ERROR: zip_fread %ld bytes from '%s'\n", zs.size, filename);
+ free(*buffer);
+ *buffer = NULL;
+ zip_fclose(zfile);
+ return -5;
+ }
+ *len = zs.size;
+ zip_fclose(zfile);
+ return 0;
+}
+
+static void do_wait_when_needed()
+{
+ 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++;
+ }
+
+ /* wait some time if a notification is expected */
+ while (notification_expected && !notified && !err_occured && (i < 10)) {
+ nanosleep(&ts, NULL);
+ i++;
+ }
+}
+
+static 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
+ (" -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");
+}
+
+static void parse_opts(int argc, char **argv)
+{
+ static struct option longopts[] = {
+ {"help", 0, NULL, 'h'},
+ {"uuid", 0, NULL, 'U'},
+ {"list-apps", 0, NULL, 'l'},
+ {"install", 0, NULL, 'i'},
+ {"uninstall", 0, NULL, 'u'},
+ {"upgrade", 0, NULL, 'g'},
+ {"list-archives", 0, NULL, 'L'},
+ {"archive", 0, NULL, 'a'},
+ {"restore", 0, NULL, 'r'},
+ {"remove-archive", 0, NULL, 'R'},
+ {"options", 0, NULL, 'o'},
+ {"debug", 0, NULL, 'D'},
+ {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);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'h':
+ print_usage(argc, argv);
+ exit(0);
+ 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);
+ 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;
+ break;
+ case 'a':
+ archive_mode = 1;
+ appid = strdup(optarg);
+ break;
+ case 'r':
+ restore_mode = 1;
+ appid = strdup(optarg);
+ break;
+ case 'R':
+ remove_archive_mode = 1;
+ appid = 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;
+ }
+ break;
+ case 'D':
+ idevice_set_debug_level(1);
+ break;
+ default:
+ print_usage(argc, argv);
+ exit(2);
+ }
+ }
+
+ if (optind <= 1 || (argc - optind > 0)) {
+ print_usage(argc, argv);
+ exit(2);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ idevice_t phone = NULL;
+ lockdownd_client_t client = NULL;
+ instproxy_client_t ipc = NULL;
+ np_client_t np = NULL;
+ afc_client_t afc = NULL;
+ uint16_t port = 0;
+ int res = 0;
+
+ 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 (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "ideviceinstaller")) {
+ fprintf(stderr, "Could not connect to lockdownd. Exiting.\n");
+ goto leave_cleanup;
+ }
+
+ 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");
+ 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;
+ }
+
+ np_set_notify_callback(np, notifier);
+
+ 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");
+ goto leave_cleanup;
+ }
+
+ if (instproxy_client_new(phone, port, &ipc) != 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;
+ }
+ notification_expected = 0;
+
+ if (list_apps_mode) {
+ int xml_mode = 0;
+ 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, ",");
+ }
+ }
+
+ 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 (!apps || (plist_get_node_type(apps) != PLIST_ARRAY)) {
+ fprintf(stderr,
+ "ERROR: instproxy_browse returnd an invalid plist!\n");
+ goto leave_cleanup;
+ }
+ if (xml_mode) {
+ char *xml = NULL;
+ uint32_t len = 0;
+
+ plist_to_xml(apps, &xml, &len);
+ if (xml) {
+ puts(xml);
+ free(xml);
+ }
+ plist_free(apps);
+ 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);
+ }
+
+ 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);
+ }
+ plist_free(apps);
+ } else if (install_mode || upgrade_mode) {
+ 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");
+ goto leave_cleanup;
+ }
+
+ lockdownd_client_free(client);
+ client = NULL;
+
+ if (afc_client_new(phone, port, &afc) != INSTPROXY_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));
+ goto leave_cleanup;
+ }
+
+ /* 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;
+ }
+
+ /* extract iTunesMetadata.plist from package */
+ char *zbuf = NULL;
+ uint32_t len = 0;
+ if (zip_f_get_contents(zf, "iTunesMetadata.plist", 0, &zbuf, &len) < 0) {
+ zip_unchange_all(zf);
+ zip_close(zf);
+ goto leave_cleanup;
+ }
+ meta = plist_new_data(zbuf, len);
+ free(zbuf);
+
+ /* 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);
+
+ if (!info) {
+ fprintf(stderr, "Could not parse Info.plist!\n");
+ zip_unchange_all(zf);
+ zip_close(zf);
+ goto leave_cleanup;
+ }
+
+ char *bundlename = NULL;
+
+ plist_t bname = plist_dict_get_item(info, "CFBundleName");
+ if (bname) {
+ plist_get_string_val(bname, &bundlename);
+ }
+ plist_free(info);
+
+ if (!bundlename) {
+ fprintf(stderr, "Could not determine CFBundleName!\n");
+ zip_unchange_all(zf);
+ zip_close(zf);
+ goto leave_cleanup;
+ }
+
+ 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);
+
+ /* 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);
+ }
+
+ zip_unchange_all(zf);
+ zip_close(zf);
+
+ /* copy archive to device */
+ f = fopen(appid, "r");
+ if (!f) {
+ fprintf(stderr, "fopen: %s: %s\n", appid, strerror(errno));
+ goto leave_cleanup;
+ }
+
+ pkgname = NULL;
+ if (asprintf(&pkgname, "%s/%s", PKG_PATH, basename(appid)) < 0) {
+ fprintf(stderr, "Out of memory!?\n");
+ goto leave_cleanup;
+ }
+
+ printf("Copying '%s' --> '%s'\n", appid, pkgname);
+
+ 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 ((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;
+ }
+
+ 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 (total != amount) {
+ fprintf(stderr, "Error: wrote only %d of %d\n", total,
+ amount);
+ afc_file_close(afc, af);
+ fclose(f);
+ free(pkgname);
+ goto leave_cleanup;
+ }
+ }
+ }
+ while (amount > 0);
+
+ afc_file_close(afc, af);
+ fclose(f);
+
+ printf("done.\n");
+
+ /* perform installation or upgrade */
+ plist_t client_opts = instproxy_client_options_new();
+ if (sinf) {
+ instproxy_client_options_add(client_opts, "ApplicationSINF", sinf, NULL);
+ }
+ if (meta) {
+ instproxy_client_options_add(client_opts, "iTunesMetadata", meta, NULL);
+ }
+ if (install_mode) {
+ printf("Installing '%s'\n", pkgname);
+ instproxy_install(ipc, pkgname, client_opts, status_cb);
+ } else {
+ printf("Upgrading '%s'\n", pkgname);
+ instproxy_upgrade(ipc, pkgname, client_opts, status_cb);
+ }
+ instproxy_client_options_free(client_opts);
+ free(pkgname);
+ wait_for_op_complete = 1;
+ notification_expected = 1;
+ } else if (uninstall_mode) {
+ instproxy_uninstall(ipc, appid, NULL, status_cb);
+ wait_for_op_complete = 1;
+ notification_expected = 1;
+ } else if (list_archives_mode) {
+ int xml_mode = 0;
+ 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");
+ goto leave_cleanup;
+ }
+
+ if (xml_mode) {
+ char *xml = NULL;
+ uint32_t len = 0;
+
+ plist_to_xml(lres, &xml, &len);
+ if (xml) {
+ puts(xml);
+ free(xml);
+ }
+ plist_free(dict);
+ goto leave_cleanup;
+ }
+ plist_dict_iter iter = NULL;
+ plist_t node = NULL;
+ char *key = NULL;
+
+ printf("Total: %d archived apps\n", plist_dict_get_size(lres));
+ plist_dict_new_iter(lres, &iter);
+ if (!iter) {
+ plist_free(dict);
+ fprintf(stderr, "ERROR: Could not create plist_dict_iter!\n");
+ goto leave_cleanup;
+ }
+ do {
+ key = NULL;
+ node = NULL;
+ plist_dict_next_item(lres, 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");
+ if (dispName) {
+ plist_get_string_val(dispName, &s_dispName);
+ }
+ if (version) {
+ plist_get_string_val(version, &s_version);
+ }
+ if (!s_dispName) {
+ s_dispName = strdup(key);
+ }
+ if (s_version) {
+ printf("%s - %s %s\n", key, s_dispName, s_version);
+ free(s_version);
+ } else {
+ printf("%s - %s\n", key, s_dispName);
+ }
+ free(s_dispName);
+ free(key);
+ }
+ }
+ 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;
+ 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) {
+ 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);
+ }
+ }
+
+ if (copy_path) {
+ 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) {
+ 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) {
+ fprintf(stderr, "Could not connect to AFC!\n");
+ goto leave_cleanup;
+ }
+ }
+
+ instproxy_archive(ipc, appid, client_opts, status_cb);
+ instproxy_client_options_free(client_opts);
+ wait_for_op_complete = 1;
+ if (skip_uninstall) {
+ notification_expected = 0;
+ } else {
+ notification_expected = 1;
+ }
+
+ do_wait_when_needed();
+
+ if (copy_path) {
+ if (err_occured) {
+ afc_client_free(afc);
+ afc = NULL;
+ goto leave_cleanup;
+ }
+ FILE *f = NULL;
+ uint64_t af = 0;
+ /* local filename */
+ char *localfile = NULL;
+ if (asprintf(&localfile, "%s/%s.ipa", copy_path, appid) < 0) {
+ fprintf(stderr, "Out of memory!?\n");
+ goto leave_cleanup;
+ }
+ free(copy_path);
+
+ f = fopen(localfile, "w");
+ if (!f) {
+ fprintf(stderr, "ERROR: fopen: %s: %s\n", localfile, strerror(errno));
+ free(localfile);
+ goto leave_cleanup;
+ }
+
+ /* remote filename */
+ char *remotefile = NULL;
+ if (asprintf(&remotefile, "%s/%s.zip", APPARCH_PATH, appid) < 0) {
+ fprintf(stderr, "Out of memory!?\n");
+ goto leave_cleanup;
+ }
+
+ uint32_t fsize = 0;
+ char **fileinfo = NULL;
+ if ((afc_get_file_info(afc, remotefile, &fileinfo) != AFC_E_SUCCESS) || !fileinfo) {
+ fprintf(stderr, "ERROR getting AFC file info for '%s' on device!\n", remotefile);
+ fclose(f);
+ free(remotefile);
+ free(localfile);
+ goto leave_cleanup;
+ }
+
+ int i;
+ for (i = 0; fileinfo[i]; i+=2) {
+ if (!strcmp(fileinfo[i], "st_size")) {
+ fsize = atoi(fileinfo[i+1]);
+ break;
+ }
+ }
+ i = 0;
+ while (fileinfo[i]) {
+ free(fileinfo[i]);
+ i++;
+ }
+ free(fileinfo);
+
+ if (fsize == 0) {
+ fprintf(stderr, "Hm... remote file length could not be determined. Cannot copy.\n");
+ fclose(f);
+ free(remotefile);
+ free(localfile);
+ goto leave_cleanup;
+ }
+
+ if ((afc_file_open(afc, remotefile, AFC_FOPEN_RDONLY, &af) != AFC_E_SUCCESS) || !af) {
+ fclose(f);
+ fprintf(stderr, "ERROR: could not open '%s' on device for reading!\n", remotefile);
+ free(remotefile);
+ free(localfile);
+ goto leave_cleanup;
+ }
+
+ /* copy file over */
+ printf("Copying '%s' --> '%s'\n", remotefile, localfile);
+ free(remotefile);
+ free(localfile);
+
+ uint32_t amount = 0;
+ uint32_t total = 0;
+ char buf[8192];
+
+ do {
+ if (afc_file_read(afc, af, buf, sizeof(buf), &amount) != AFC_E_SUCCESS) {
+ fprintf(stderr, "AFC Read error!\n");
+ break;
+ }
+
+ if (amount > 0) {
+ size_t written = fwrite(buf, 1, amount, f);
+ if (written != amount) {
+ fprintf(stderr, "Error when writing %d bytes to local file!\n", amount);
+ break;
+ }
+ total += written;
+ }
+ } while (amount > 0);
+
+ afc_file_close(afc, af);
+ fclose(f);
+
+ 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) {
+ fprintf(stderr, "NOTE: archive file will NOT be removed from device\n");
+ remove_after_copy = 0;
+ }
+ }
+
+ 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")) {
+ fprintf(stderr, "Could not connect to lockdownd. Exiting.\n");
+ goto leave_cleanup;
+ }
+ goto run_again;
+ }
+ }
+ goto leave_cleanup;
+ } else if (restore_mode) {
+ instproxy_restore(ipc, appid, NULL, status_cb);
+ wait_for_op_complete = 1;
+ notification_expected = 1;
+ } else if (remove_archive_mode) {
+ instproxy_remove_archive(ipc, appid, NULL, status_cb);
+ wait_for_op_complete = 1;
+ } else {
+ printf
+ ("ERROR: no operation 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);
+ }
+
+ return res;
+}