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 @@
1/**
2 * ideviceinstaller -- Manage iPhone/iPod apps
3 *
4 * Copyright (C) 2010 Nikias Bassen <nikias@gmx.li>
5 *
6 * Licensed under the GNU General Public License Version 2
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more profile.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
21 * USA
22 */
23#include <stdlib.h>
24#define _GNU_SOURCE 1
25#define __USE_GNU 1
26#include <stdio.h>
27#include <string.h>
28#include <getopt.h>
29#include <errno.h>
30#include <time.h>
31
32#include <libimobiledevice/libimobiledevice.h>
33#include <libimobiledevice/lockdown.h>
34#include <libimobiledevice/installation_proxy.h>
35#include <libimobiledevice/notification_proxy.h>
36#include <libimobiledevice/afc.h>
37
38#include <plist/plist.h>
39
40#include <zip.h>
41
42const char PKG_PATH[] = "PublicStaging";
43const char APPARCH_PATH[] = "ApplicationArchives";
44
45char *uuid = NULL;
46char *options = NULL;
47char *appid = NULL;
48
49int list_apps_mode = 0;
50int install_mode = 0;
51int uninstall_mode = 0;
52int upgrade_mode = 0;
53int list_archives_mode = 0;
54int archive_mode = 0;
55int restore_mode = 0;
56int remove_archive_mode = 0;
57
58char *last_status = NULL;
59int wait_for_op_complete = 0;
60int notification_expected = 0;
61int op_completed = 0;
62int err_occured = 0;
63int notified = 0;
64
65
66static void notifier(const char *notification)
67{
68 /* printf("notification received: %s\n", notification);*/
69 notified = 1;
70}
71
72static void status_cb(const char *operation, plist_t status)
73{
74 if (status && operation) {
75 plist_t npercent = plist_dict_get_item(status, "PercentComplete");
76 plist_t nstatus = plist_dict_get_item(status, "Status");
77 plist_t nerror = plist_dict_get_item(status, "Error");
78 int percent = 0;
79 char *status_msg = NULL;
80 if (npercent) {
81 uint64_t val = 0;
82 plist_get_uint_val(npercent, &val);
83 percent = val;
84 }
85 if (nstatus) {
86 plist_get_string_val(nstatus, &status_msg);
87 if (!strcmp(status_msg, "Complete")) {
88 op_completed = 1;
89 }
90 }
91 if (!nerror) {
92 if (last_status && (strcmp(last_status, status_msg))) {
93 printf("\n");
94 }
95
96 if (!npercent) {
97 printf("%s - %s\n", operation, status_msg);
98 } else {
99 printf("%s - %s (%d%%)\r", operation, status_msg, percent);
100 }
101 } else {
102 char *err_msg = NULL;
103 plist_get_string_val(nerror, &err_msg);
104 printf("%s - Error occured: %s\n", operation, err_msg);
105 free(err_msg);
106 err_occured = 1;
107 }
108 if (last_status) {
109 free(last_status);
110 last_status = NULL;
111 }
112 if (status_msg) {
113 last_status = strdup(status_msg);
114 free(status_msg);
115 }
116 } else {
117 printf("%s: called with invalid data!\n", __func__);
118 }
119}
120
121static int zip_f_get_contents(struct zip *zf, const char *filename, int locate_flags, char **buffer, uint32_t *len)
122{
123 struct zip_stat zs;
124 struct zip_file *zfile;
125 int zindex = zip_name_locate(zf, filename, locate_flags);
126
127 *buffer = NULL;
128 *len = 0;
129
130 if (zindex < 0) {
131 fprintf(stderr, "ERROR: could not locate %s in archive!\n", filename);
132 return -1;
133 }
134
135 zip_stat_init(&zs);
136
137 if (zip_stat_index(zf, zindex, 0, &zs) != 0) {
138 fprintf(stderr, "ERROR: zip_stat_index '%s' failed!\n", filename);
139 return -2;
140 }
141
142 if (zs.size > 10485760) {
143 fprintf(stderr, "ERROR: file '%s' is too large!\n", filename);
144 return -3;
145 }
146
147 zfile = zip_fopen_index(zf, zindex, 0);
148 if (!zfile) {
149 fprintf(stderr, "ERROR: zip_fopen '%s' failed!\n", filename);
150 return -4;
151 }
152
153 *buffer = malloc(zs.size);
154 if (zip_fread(zfile, *buffer, zs.size) != zs.size) {
155 fprintf(stderr, "ERROR: zip_fread %ld bytes from '%s'\n", zs.size, filename);
156 free(*buffer);
157 *buffer = NULL;
158 zip_fclose(zfile);
159 return -5;
160 }
161 *len = zs.size;
162 zip_fclose(zfile);
163 return 0;
164}
165
166static void do_wait_when_needed()
167{
168 int i = 0;
169 struct timespec ts;
170 ts.tv_sec = 0;
171 ts.tv_nsec = 500000000;
172
173 /* wait for operation to complete */
174 while (wait_for_op_complete && !op_completed && !err_occured
175 && !notified && (i < 60)) {
176 nanosleep(&ts, NULL);
177 i++;
178 }
179
180 /* wait some time if a notification is expected */
181 while (notification_expected && !notified && !err_occured && (i < 10)) {
182 nanosleep(&ts, NULL);
183 i++;
184 }
185}
186
187static void print_usage(int argc, char **argv)
188{
189 char *name = NULL;
190
191 name = strrchr(argv[0], '/');
192 printf("Usage: %s OPTIONS\n", (name ? name + 1 : argv[0]));
193 printf
194 (" -U|--uuid UUID\tTarget specific device by its 40-digit device UUID.\n"
195 " -l|--list-apps\tList apps, possible options:\n"
196 " -o list_user\t- list user apps only (this is the default)\n"
197 " -o list_system\t- list system apps only\n"
198 " -o list_all\t- list all types of apps\n"
199 " -o xml\t\t- print full output as xml plist\n"
200 " -i|--install ARCHIVE\tInstall app from package file specified by ARCHIVE.\n"
201 " -u|--uninstall APPID\tUninstall app specified by APPID.\n"
202 " -g|--upgrade APPID\tUpgrade app specified by APPID.\n"
203 " -L|--list-archives\tList archived applications, possible options:\n"
204 " -o xml\t\t- print full output as xml plist\n"
205 " -a|--archive APPID\tArchive app specified by APPID, possible options:\n"
206 " -o uninstall\t- uninstall the package after making an archive\n"
207 " -o app_only\t- archive application data only\n"
208 " -o copy=PATH\t- copy the app archive to directory PATH when done\n"
209 " -o remove\t- only valid when copy=PATH is used: remove after copy\n"
210 " -r|--restore APPID\tRestore archived app specified by APPID\n"
211 " -R|--remove-archive APPID Remove app archive specified by APPID\n"
212 " -o|--options\t\tPass additional options to the specified command.\n"
213 " -h|--help\t\tprints usage information\n"
214 " -D|--debug\t\tenable communication debugging\n" "\n");
215}
216
217static void parse_opts(int argc, char **argv)
218{
219 static struct option longopts[] = {
220 {"help", 0, NULL, 'h'},
221 {"uuid", 0, NULL, 'U'},
222 {"list-apps", 0, NULL, 'l'},
223 {"install", 0, NULL, 'i'},
224 {"uninstall", 0, NULL, 'u'},
225 {"upgrade", 0, NULL, 'g'},
226 {"list-archives", 0, NULL, 'L'},
227 {"archive", 0, NULL, 'a'},
228 {"restore", 0, NULL, 'r'},
229 {"remove-archive", 0, NULL, 'R'},
230 {"options", 0, NULL, 'o'},
231 {"debug", 0, NULL, 'D'},
232 {NULL, 0, NULL, 0}
233 };
234 int c;
235
236 while (1) {
237 c = getopt_long(argc, argv, "hU:li:u:g:La:r:R:o:D", longopts,
238 (int *) 0);
239 if (c == -1) {
240 break;
241 }
242
243 switch (c) {
244 case 'h':
245 print_usage(argc, argv);
246 exit(0);
247 case 'U':
248 if (strlen(optarg) != 40) {
249 printf("%s: invalid UUID specified (length != 40)\n",
250 argv[0]);
251 print_usage(argc, argv);
252 exit(2);
253 }
254 uuid = strdup(optarg);
255 break;
256 case 'l':
257 list_apps_mode = 1;
258 break;
259 case 'i':
260 install_mode = 1;
261 appid = strdup(optarg);
262 break;
263 case 'u':
264 uninstall_mode = 1;
265 appid = strdup(optarg);
266 break;
267 case 'g':
268 upgrade_mode = 1;
269 appid = strdup(optarg);
270 break;
271 case 'L':
272 list_archives_mode = 1;
273 break;
274 case 'a':
275 archive_mode = 1;
276 appid = strdup(optarg);
277 break;
278 case 'r':
279 restore_mode = 1;
280 appid = strdup(optarg);
281 break;
282 case 'R':
283 remove_archive_mode = 1;
284 appid = strdup(optarg);
285 break;
286 case 'o':
287 if (!options) {
288 options = strdup(optarg);
289 } else {
290 char *newopts = malloc(strlen(options) + strlen(optarg) + 2);
291 strcpy(newopts, options);
292 free(options);
293 strcat(newopts, ",");
294 strcat(newopts, optarg);
295 options = newopts;
296 }
297 break;
298 case 'D':
299 idevice_set_debug_level(1);
300 break;
301 default:
302 print_usage(argc, argv);
303 exit(2);
304 }
305 }
306
307 if (optind <= 1 || (argc - optind > 0)) {
308 print_usage(argc, argv);
309 exit(2);
310 }
311}
312
313int main(int argc, char **argv)
314{
315 idevice_t phone = NULL;
316 lockdownd_client_t client = NULL;
317 instproxy_client_t ipc = NULL;
318 np_client_t np = NULL;
319 afc_client_t afc = NULL;
320 uint16_t port = 0;
321 int res = 0;
322
323 parse_opts(argc, argv);
324
325 argc -= optind;
326 argv += optind;
327
328 if (IDEVICE_E_SUCCESS != idevice_new(&phone, uuid)) {
329 fprintf(stderr, "No iPhone found, is it plugged in?\n");
330 return -1;
331 }
332
333 if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "ideviceinstaller")) {
334 fprintf(stderr, "Could not connect to lockdownd. Exiting.\n");
335 goto leave_cleanup;
336 }
337
338 if ((lockdownd_start_service
339 (client, "com.apple.mobile.notification_proxy",
340 &port) != LOCKDOWN_E_SUCCESS) || !port) {
341 fprintf(stderr,
342 "Could not start com.apple.mobile.notification_proxy!\n");
343 goto leave_cleanup;
344 }
345
346 if (np_client_new(phone, port, &np) != NP_E_SUCCESS) {
347 fprintf(stderr, "Could not connect to notification_proxy!\n");
348 goto leave_cleanup;
349 }
350
351 np_set_notify_callback(np, notifier);
352
353 const char *noties[3] = { NP_APP_INSTALLED, NP_APP_UNINSTALLED, NULL };
354
355 np_observe_notifications(np, noties);
356
357run_again:
358 port = 0;
359 if ((lockdownd_start_service
360 (client, "com.apple.mobile.installation_proxy",
361 &port) != LOCKDOWN_E_SUCCESS) || !port) {
362 fprintf(stderr,
363 "Could not start com.apple.mobile.installation_proxy!\n");
364 goto leave_cleanup;
365 }
366
367 if (instproxy_client_new(phone, port, &ipc) != INSTPROXY_E_SUCCESS) {
368 fprintf(stderr, "Could not connect to installation_proxy!\n");
369 goto leave_cleanup;
370 }
371
372 setbuf(stdout, NULL);
373
374 if (last_status) {
375 free(last_status);
376 last_status = NULL;
377 }
378 notification_expected = 0;
379
380 if (list_apps_mode) {
381 int xml_mode = 0;
382 plist_t client_opts = instproxy_client_options_new();
383 instproxy_client_options_add(client_opts, "ApplicationType", "User", NULL);
384 instproxy_error_t err;
385 plist_t apps = NULL;
386
387 /* look for options */
388 if (options) {
389 char *opts = strdup(options);
390 char *elem = strtok(opts, ",");
391 while (elem) {
392 if (!strcmp(elem, "list_system")) {
393 if (!client_opts) {
394 client_opts = instproxy_client_options_new();
395 }
396 instproxy_client_options_add(client_opts, "ApplicationType", "System", NULL);
397 } else if (!strcmp(elem, "list_all")) {
398 instproxy_client_options_free(client_opts);
399 client_opts = NULL;
400 } else if (!strcmp(elem, "list_user")) {
401 /* do nothing, we're already set */
402 } else if (!strcmp(elem, "xml")) {
403 xml_mode = 1;
404 }
405 elem = strtok(NULL, ",");
406 }
407 }
408
409 err = instproxy_browse(ipc, client_opts, &apps);
410 instproxy_client_options_free(client_opts);
411 if (err != INSTPROXY_E_SUCCESS) {
412 fprintf(stderr, "ERROR: instproxy_browse returned %d\n", err);
413 goto leave_cleanup;
414 }
415 if (!apps || (plist_get_node_type(apps) != PLIST_ARRAY)) {
416 fprintf(stderr,
417 "ERROR: instproxy_browse returnd an invalid plist!\n");
418 goto leave_cleanup;
419 }
420 if (xml_mode) {
421 char *xml = NULL;
422 uint32_t len = 0;
423
424 plist_to_xml(apps, &xml, &len);
425 if (xml) {
426 puts(xml);
427 free(xml);
428 }
429 plist_free(apps);
430 goto leave_cleanup;
431 }
432 printf("Total: %d apps\n", plist_array_get_size(apps));
433 uint32_t i = 0;
434 for (i = 0; i < plist_array_get_size(apps); i++) {
435 plist_t app = plist_array_get_item(apps, i);
436 plist_t p_appid =
437 plist_dict_get_item(app, "CFBundleIdentifier");
438 char *s_appid = NULL;
439 char *s_dispName = NULL;
440 char *s_version = NULL;
441 plist_t dispName =
442 plist_dict_get_item(app, "CFBundleDisplayName");
443 plist_t version = plist_dict_get_item(app, "CFBundleVersion");
444
445 if (p_appid) {
446 plist_get_string_val(p_appid, &s_appid);
447 }
448 if (!s_appid) {
449 fprintf(stderr, "ERROR: Failed to get APPID!\n");
450 break;
451 }
452
453 if (dispName) {
454 plist_get_string_val(dispName, &s_dispName);
455 }
456 if (version) {
457 plist_get_string_val(version, &s_version);
458 }
459
460 if (!s_dispName) {
461 s_dispName = strdup(s_appid);
462 }
463 if (s_version) {
464 printf("%s - %s %s\n", s_appid, s_dispName, s_version);
465 free(s_version);
466 } else {
467 printf("%s - %s\n", s_appid, s_dispName);
468 }
469 free(s_dispName);
470 free(s_appid);
471 }
472 plist_free(apps);
473 } else if (install_mode || upgrade_mode) {
474 plist_t sinf = NULL;
475 plist_t meta = NULL;
476 char *pkgname = NULL;
477 struct stat fst;
478 FILE *f = NULL;
479 uint64_t af = 0;
480 char buf[8192];
481
482 port = 0;
483 if ((lockdownd_start_service(client, "com.apple.afc", &port) !=
484 LOCKDOWN_E_SUCCESS) || !port) {
485 fprintf(stderr, "Could not start com.apple.afc!\n");
486 goto leave_cleanup;
487 }
488
489 lockdownd_client_free(client);
490 client = NULL;
491
492 if (afc_client_new(phone, port, &afc) != INSTPROXY_E_SUCCESS) {
493 fprintf(stderr, "Could not connect to AFC!\n");
494 goto leave_cleanup;
495 }
496
497 if (stat(appid, &fst) != 0) {
498 fprintf(stderr, "ERROR: stat: %s: %s\n", appid, strerror(errno));
499 goto leave_cleanup;
500 }
501
502 /* open install package */
503 int errp = 0;
504 struct zip *zf = zip_open(appid, 0, &errp);
505 if (!zf) {
506 fprintf(stderr, "ERROR: zip_open: %s: %d\n", appid, errp);
507 goto leave_cleanup;
508 }
509
510 /* extract iTunesMetadata.plist from package */
511 char *zbuf = NULL;
512 uint32_t len = 0;
513 if (zip_f_get_contents(zf, "iTunesMetadata.plist", 0, &zbuf, &len) < 0) {
514 zip_unchange_all(zf);
515 zip_close(zf);
516 goto leave_cleanup;
517 }
518 meta = plist_new_data(zbuf, len);
519 free(zbuf);
520
521 /* we need to get the CFBundleName first */
522 plist_t info = NULL;
523 zbuf = NULL;
524 len = 0;
525 if (zip_f_get_contents(zf, "Info.plist", ZIP_FL_NODIR, &zbuf, &len) < 0) {
526 zip_unchange_all(zf);
527 zip_close(zf);
528 goto leave_cleanup;
529 }
530 if (memcmp(zbuf, "bplist00", 8) == 0) {
531 plist_from_bin(zbuf, len, &info);
532 } else {
533 plist_from_xml(zbuf, len, &info);
534 }
535 free(zbuf);
536
537 if (!info) {
538 fprintf(stderr, "Could not parse Info.plist!\n");
539 zip_unchange_all(zf);
540 zip_close(zf);
541 goto leave_cleanup;
542 }
543
544 char *bundlename = NULL;
545
546 plist_t bname = plist_dict_get_item(info, "CFBundleName");
547 if (bname) {
548 plist_get_string_val(bname, &bundlename);
549 }
550 plist_free(info);
551
552 if (!bundlename) {
553 fprintf(stderr, "Could not determine CFBundleName!\n");
554 zip_unchange_all(zf);
555 zip_close(zf);
556 goto leave_cleanup;
557 }
558
559 char *sinfname = NULL;
560 if (asprintf(&sinfname, "Payload/%s.app/SC_Info/%s.sinf", bundlename, bundlename) < 0) {
561 fprintf(stderr, "Out of memory!?\n");
562 goto leave_cleanup;
563 }
564 free(bundlename);
565
566 /* extract .sinf from package */
567 zbuf = NULL;
568 len = 0;
569 if (zip_f_get_contents(zf, sinfname, 0, &zbuf, &len) == 0) {
570 sinf = plist_new_data(zbuf, len);
571 }
572 free(sinfname);
573 if (zbuf) {
574 free(zbuf);
575 }
576
577 zip_unchange_all(zf);
578 zip_close(zf);
579
580 /* copy archive to device */
581 f = fopen(appid, "r");
582 if (!f) {
583 fprintf(stderr, "fopen: %s: %s\n", appid, strerror(errno));
584 goto leave_cleanup;
585 }
586
587 pkgname = NULL;
588 if (asprintf(&pkgname, "%s/%s", PKG_PATH, basename(appid)) < 0) {
589 fprintf(stderr, "Out of memory!?\n");
590 goto leave_cleanup;
591 }
592
593 printf("Copying '%s' --> '%s'\n", appid, pkgname);
594
595 char **strs = NULL;
596 if (afc_get_file_info(afc, PKG_PATH, &strs) != AFC_E_SUCCESS) {
597 if (afc_make_directory(afc, PKG_PATH) != AFC_E_SUCCESS) {
598 fprintf(stderr, "WARNING: Could not create directory '%s' on device!\n", PKG_PATH);
599 }
600 }
601 if (strs) {
602 int i = 0;
603 while (strs[i]) {
604 free(strs[i]);
605 i++;
606 }
607 free(strs);
608 }
609
610 if ((afc_file_open(afc, pkgname, AFC_FOPEN_WRONLY, &af) !=
611 AFC_E_SUCCESS) || !af) {
612 fclose(f);
613 fprintf(stderr, "afc_file_open on '%s' failed!\n", pkgname);
614 free(pkgname);
615 goto leave_cleanup;
616 }
617
618 size_t amount = 0;
619 do {
620 amount = fread(buf, 1, sizeof(buf), f);
621 if (amount > 0) {
622 uint32_t written, total = 0;
623 while (total < amount) {
624 written = 0;
625 if (afc_file_write(afc, af, buf, amount, &written) !=
626 AFC_E_SUCCESS) {
627 fprintf(stderr, "AFC Write error!\n");
628 break;
629 }
630 total += written;
631 }
632 if (total != amount) {
633 fprintf(stderr, "Error: wrote only %d of %d\n", total,
634 amount);
635 afc_file_close(afc, af);
636 fclose(f);
637 free(pkgname);
638 goto leave_cleanup;
639 }
640 }
641 }
642 while (amount > 0);
643
644 afc_file_close(afc, af);
645 fclose(f);
646
647 printf("done.\n");
648
649 /* perform installation or upgrade */
650 plist_t client_opts = instproxy_client_options_new();
651 if (sinf) {
652 instproxy_client_options_add(client_opts, "ApplicationSINF", sinf, NULL);
653 }
654 if (meta) {
655 instproxy_client_options_add(client_opts, "iTunesMetadata", meta, NULL);
656 }
657 if (install_mode) {
658 printf("Installing '%s'\n", pkgname);
659 instproxy_install(ipc, pkgname, client_opts, status_cb);
660 } else {
661 printf("Upgrading '%s'\n", pkgname);
662 instproxy_upgrade(ipc, pkgname, client_opts, status_cb);
663 }
664 instproxy_client_options_free(client_opts);
665 free(pkgname);
666 wait_for_op_complete = 1;
667 notification_expected = 1;
668 } else if (uninstall_mode) {
669 instproxy_uninstall(ipc, appid, NULL, status_cb);
670 wait_for_op_complete = 1;
671 notification_expected = 1;
672 } else if (list_archives_mode) {
673 int xml_mode = 0;
674 plist_t dict = NULL;
675 plist_t lres = NULL;
676 instproxy_error_t err;
677
678 /* look for options */
679 if (options) {
680 char *opts = strdup(options);
681 char *elem = strtok(opts, ",");
682 while (elem) {
683 if (!strcmp(elem, "xml")) {
684 xml_mode = 1;
685 }
686 elem = strtok(NULL, ",");
687 }
688 }
689
690 err = instproxy_lookup_archives(ipc, NULL, &dict);
691 if (err != INSTPROXY_E_SUCCESS) {
692 fprintf(stderr, "ERROR: lookup_archives returned %d\n", err);
693 goto leave_cleanup;
694 }
695 if (!dict) {
696 fprintf(stderr,
697 "ERROR: lookup_archives did not return a plist!?\n");
698 goto leave_cleanup;
699 }
700
701 lres = plist_dict_get_item(dict, "LookupResult");
702 if (!lres || (plist_get_node_type(lres) != PLIST_DICT)) {
703 plist_free(dict);
704 fprintf(stderr, "ERROR: Could not get dict 'LookupResult'\n");
705 goto leave_cleanup;
706 }
707
708 if (xml_mode) {
709 char *xml = NULL;
710 uint32_t len = 0;
711
712 plist_to_xml(lres, &xml, &len);
713 if (xml) {
714 puts(xml);
715 free(xml);
716 }
717 plist_free(dict);
718 goto leave_cleanup;
719 }
720 plist_dict_iter iter = NULL;
721 plist_t node = NULL;
722 char *key = NULL;
723
724 printf("Total: %d archived apps\n", plist_dict_get_size(lres));
725 plist_dict_new_iter(lres, &iter);
726 if (!iter) {
727 plist_free(dict);
728 fprintf(stderr, "ERROR: Could not create plist_dict_iter!\n");
729 goto leave_cleanup;
730 }
731 do {
732 key = NULL;
733 node = NULL;
734 plist_dict_next_item(lres, iter, &key, &node);
735 if (key && (plist_get_node_type(node) == PLIST_DICT)) {
736 char *s_dispName = NULL;
737 char *s_version = NULL;
738 plist_t dispName =
739 plist_dict_get_item(node, "CFBundleDisplayName");
740 plist_t version =
741 plist_dict_get_item(node, "CFBundleVersion");
742 if (dispName) {
743 plist_get_string_val(dispName, &s_dispName);
744 }
745 if (version) {
746 plist_get_string_val(version, &s_version);
747 }
748 if (!s_dispName) {
749 s_dispName = strdup(key);
750 }
751 if (s_version) {
752 printf("%s - %s %s\n", key, s_dispName, s_version);
753 free(s_version);
754 } else {
755 printf("%s - %s\n", key, s_dispName);
756 }
757 free(s_dispName);
758 free(key);
759 }
760 }
761 while (node);
762 plist_free(dict);
763 } else if (archive_mode) {
764 char *copy_path = NULL;
765 int remove_after_copy = 0;
766 int skip_uninstall = 1;
767 int app_only = 0;
768 plist_t client_opts = NULL;
769
770 /* look for options */
771 if (options) {
772 char *opts = strdup(options);
773 char *elem = strtok(opts, ",");
774 while (elem) {
775 if (!strcmp(elem, "uninstall")) {
776 skip_uninstall = 0;
777 } else if (!strcmp(elem, "app_only")) {
778 app_only = 1;
779 } else if ((strlen(elem) > 5) && !strncmp(elem, "copy=", 5)) {
780 copy_path = strdup(elem+5);
781 } else if (!strcmp(elem, "remove")) {
782 remove_after_copy = 1;
783 }
784 elem = strtok(NULL, ",");
785 }
786 }
787
788 if (skip_uninstall || app_only) {
789 client_opts = instproxy_client_options_new();
790 if (skip_uninstall) {
791 instproxy_client_options_add(client_opts, "SkipUninstall", 1, NULL);
792 }
793 if (app_only) {
794 instproxy_client_options_add(client_opts, "ArchiveType", "ApplicationOnly", NULL);
795 }
796 }
797
798 if (copy_path) {
799 struct stat fst;
800 if (stat(copy_path, &fst) != 0) {
801 fprintf(stderr, "ERROR: stat: %s: %s\n", copy_path, strerror(errno));
802 free(copy_path);
803 goto leave_cleanup;
804 }
805
806 if (!S_ISDIR(fst.st_mode)) {
807 fprintf(stderr, "ERROR: '%s' is not a directory as expected.\n", copy_path);
808 free(copy_path);
809 goto leave_cleanup;
810 }
811
812 port = 0;
813 if ((lockdownd_start_service(client, "com.apple.afc", &port) != LOCKDOWN_E_SUCCESS) || !port) {
814 fprintf(stderr, "Could not start com.apple.afc!\n");
815 free(copy_path);
816 goto leave_cleanup;
817 }
818
819 lockdownd_client_free(client);
820 client = NULL;
821
822 if (afc_client_new(phone, port, &afc) != INSTPROXY_E_SUCCESS) {
823 fprintf(stderr, "Could not connect to AFC!\n");
824 goto leave_cleanup;
825 }
826 }
827
828 instproxy_archive(ipc, appid, client_opts, status_cb);
829 instproxy_client_options_free(client_opts);
830 wait_for_op_complete = 1;
831 if (skip_uninstall) {
832 notification_expected = 0;
833 } else {
834 notification_expected = 1;
835 }
836
837 do_wait_when_needed();
838
839 if (copy_path) {
840 if (err_occured) {
841 afc_client_free(afc);
842 afc = NULL;
843 goto leave_cleanup;
844 }
845 FILE *f = NULL;
846 uint64_t af = 0;
847 /* local filename */
848 char *localfile = NULL;
849 if (asprintf(&localfile, "%s/%s.ipa", copy_path, appid) < 0) {
850 fprintf(stderr, "Out of memory!?\n");
851 goto leave_cleanup;
852 }
853 free(copy_path);
854
855 f = fopen(localfile, "w");
856 if (!f) {
857 fprintf(stderr, "ERROR: fopen: %s: %s\n", localfile, strerror(errno));
858 free(localfile);
859 goto leave_cleanup;
860 }
861
862 /* remote filename */
863 char *remotefile = NULL;
864 if (asprintf(&remotefile, "%s/%s.zip", APPARCH_PATH, appid) < 0) {
865 fprintf(stderr, "Out of memory!?\n");
866 goto leave_cleanup;
867 }
868
869 uint32_t fsize = 0;
870 char **fileinfo = NULL;
871 if ((afc_get_file_info(afc, remotefile, &fileinfo) != AFC_E_SUCCESS) || !fileinfo) {
872 fprintf(stderr, "ERROR getting AFC file info for '%s' on device!\n", remotefile);
873 fclose(f);
874 free(remotefile);
875 free(localfile);
876 goto leave_cleanup;
877 }
878
879 int i;
880 for (i = 0; fileinfo[i]; i+=2) {
881 if (!strcmp(fileinfo[i], "st_size")) {
882 fsize = atoi(fileinfo[i+1]);
883 break;
884 }
885 }
886 i = 0;
887 while (fileinfo[i]) {
888 free(fileinfo[i]);
889 i++;
890 }
891 free(fileinfo);
892
893 if (fsize == 0) {
894 fprintf(stderr, "Hm... remote file length could not be determined. Cannot copy.\n");
895 fclose(f);
896 free(remotefile);
897 free(localfile);
898 goto leave_cleanup;
899 }
900
901 if ((afc_file_open(afc, remotefile, AFC_FOPEN_RDONLY, &af) != AFC_E_SUCCESS) || !af) {
902 fclose(f);
903 fprintf(stderr, "ERROR: could not open '%s' on device for reading!\n", remotefile);
904 free(remotefile);
905 free(localfile);
906 goto leave_cleanup;
907 }
908
909 /* copy file over */
910 printf("Copying '%s' --> '%s'\n", remotefile, localfile);
911 free(remotefile);
912 free(localfile);
913
914 uint32_t amount = 0;
915 uint32_t total = 0;
916 char buf[8192];
917
918 do {
919 if (afc_file_read(afc, af, buf, sizeof(buf), &amount) != AFC_E_SUCCESS) {
920 fprintf(stderr, "AFC Read error!\n");
921 break;
922 }
923
924 if (amount > 0) {
925 size_t written = fwrite(buf, 1, amount, f);
926 if (written != amount) {
927 fprintf(stderr, "Error when writing %d bytes to local file!\n", amount);
928 break;
929 }
930 total += written;
931 }
932 } while (amount > 0);
933
934 afc_file_close(afc, af);
935 fclose(f);
936
937 printf("done.\n");
938 if (total != fsize) {
939 fprintf(stderr, "WARNING: remote and local file sizes don't match (%d != %d)\n", fsize, total);
940 if (remove_after_copy) {
941 fprintf(stderr, "NOTE: archive file will NOT be removed from device\n");
942 remove_after_copy = 0;
943 }
944 }
945
946 if (remove_after_copy) {
947 /* remove archive if requested */
948 printf("Removing '%s'\n", appid);
949 archive_mode = 0;
950 remove_archive_mode = 1;
951 free(options);
952 options = NULL;
953 if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "ideviceinstaller")) {
954 fprintf(stderr, "Could not connect to lockdownd. Exiting.\n");
955 goto leave_cleanup;
956 }
957 goto run_again;
958 }
959 }
960 goto leave_cleanup;
961 } else if (restore_mode) {
962 instproxy_restore(ipc, appid, NULL, status_cb);
963 wait_for_op_complete = 1;
964 notification_expected = 1;
965 } else if (remove_archive_mode) {
966 instproxy_remove_archive(ipc, appid, NULL, status_cb);
967 wait_for_op_complete = 1;
968 } else {
969 printf
970 ("ERROR: no operation selected?! This should not be reached!\n");
971 res = -2;
972 goto leave_cleanup;
973 }
974
975 if (client) {
976 /* not needed anymore */
977 lockdownd_client_free(client);
978 client = NULL;
979 }
980
981 do_wait_when_needed();
982
983 leave_cleanup:
984 if (np) {
985 np_client_free(np);
986 }
987 if (ipc) {
988 instproxy_client_free(ipc);
989 }
990 if (afc) {
991 afc_client_free(afc);
992 }
993 if (client) {
994 lockdownd_client_free(client);
995 }
996 idevice_free(phone);
997
998 if (uuid) {
999 free(uuid);
1000 }
1001 if (appid) {
1002 free(appid);
1003 }
1004 if (options) {
1005 free(options);
1006 }
1007
1008 return res;
1009}