summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorGravatar Martin Szulecki2010-01-05 04:52:50 +0100
committerGravatar Martin Szulecki2010-01-05 04:52:50 +0100
commite0cf27624accbcd49ff628919a2546685e3136d9 (patch)
tree0009f001df8e971813e363a2d8036caad261d3f0 /src
downloadideviceinstaller-e0cf27624accbcd49ff628919a2546685e3136d9.tar.gz
ideviceinstaller-e0cf27624accbcd49ff628919a2546685e3136d9.tar.bz2
Commit initial sources
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am17
-rw-r--r--src/iphoneinstaller.c982
2 files changed, 999 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..028e2e1
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,17 @@
1AM_CFLAGS = \
2 $(GLOBAL_CFLAGS) \
3 $(libiphone_CFLAGS) \
4 $(libplist_CFLAGS) \
5 $(libzip_CFLAGS)
6
7AM_LDFLAGS = \
8 $(libiphone_LIBS) \
9 $(libplist_LIBS) \
10 $(libzip_LIBS)
11
12bin_PROGRAMS = iphoneinstaller
13
14iphoneinstaller_SOURCES = iphoneinstaller.c
15iphoneinstaller_CFLAGS = $(AM_CFLAGS)
16iphoneinstaller_LDFLAGS = $(AM_LDFLAGS)
17
diff --git a/src/iphoneinstaller.c b/src/iphoneinstaller.c
new file mode 100644
index 0000000..d3e311c
--- /dev/null
+++ b/src/iphoneinstaller.c
@@ -0,0 +1,982 @@
1/**
2 * iphoneinstaller -- 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 <libiphone/libiphone.h>
33#include <libiphone/lockdown.h>
34#include <libiphone/installation_proxy.h>
35#include <libiphone/notification_proxy.h>
36#include <libiphone/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 iphone_set_debug_level(1);
300 iphone_set_debug_mask(DBGMASK_ALL);
301 break;
302 default:
303 print_usage(argc, argv);
304 exit(2);
305 }
306 }
307
308 if (optind <= 1 || (argc - optind > 0)) {
309 print_usage(argc, argv);
310 exit(2);
311 }
312}
313
314int main(int argc, char **argv)
315{
316 iphone_device_t phone = NULL;
317 lockdownd_client_t client = NULL;
318 instproxy_client_t ipc = NULL;
319 np_client_t np = NULL;
320 afc_client_t afc = NULL;
321 int port = 0;
322 int res = 0;
323
324 parse_opts(argc, argv);
325
326 argc -= optind;
327 argv += optind;
328
329 if (IPHONE_E_SUCCESS != iphone_device_new(&phone, uuid)) {
330 fprintf(stderr, "No iPhone found, is it plugged in?\n");
331 return -1;
332 }
333
334 if (LOCKDOWN_E_SUCCESS != lockdownd_client_new(phone, &client)) {
335 fprintf(stderr, "Could not connect to lockdownd. Exiting.\n");
336 goto leave_cleanup;
337 }
338
339 if ((lockdownd_start_service
340 (client, "com.apple.mobile.notification_proxy",
341 &port) != LOCKDOWN_E_SUCCESS) || !port) {
342 fprintf(stderr,
343 "Could not start com.apple.mobile.notification_proxy!\n");
344 goto leave_cleanup;
345 }
346
347 if (np_client_new(phone, port, &np) != NP_E_SUCCESS) {
348 fprintf(stderr, "Could not connect to notification_proxy!\n");
349 goto leave_cleanup;
350 }
351
352 np_set_notify_callback(np, notifier);
353
354 const char *noties[3] = { NP_APP_INSTALLED, NP_APP_UNINSTALLED, NULL };
355
356 np_observe_notifications(np, noties);
357
358run_again:
359 port = 0;
360 if ((lockdownd_start_service
361 (client, "com.apple.mobile.installation_proxy",
362 &port) != LOCKDOWN_E_SUCCESS) || !port) {
363 fprintf(stderr,
364 "Could not start com.apple.mobile.installation_proxy!\n");
365 goto leave_cleanup;
366 }
367
368 if (instproxy_client_new(phone, port, &ipc) != INSTPROXY_E_SUCCESS) {
369 fprintf(stderr, "Could not connect to installation_proxy!\n");
370 goto leave_cleanup;
371 }
372
373 setbuf(stdout, NULL);
374
375 if (last_status) {
376 free(last_status);
377 last_status = NULL;
378 }
379 notification_expected = 0;
380
381 if (list_apps_mode) {
382 int xml_mode = 0;
383 instproxy_apptype_t apptype = INSTPROXY_APPTYPE_USER;
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 apptype = INSTPROXY_APPTYPE_SYSTEM;
394 } else if (!strcmp(elem, "list_all")) {
395 apptype = INSTPROXY_APPTYPE_ALL;
396 } else if (!strcmp(elem, "list_user")) {
397 apptype = INSTPROXY_APPTYPE_USER;
398 } else if (!strcmp(elem, "xml")) {
399 xml_mode = 1;
400 }
401 elem = strtok(NULL, ",");
402 }
403 }
404
405 err = instproxy_browse(ipc, apptype, &apps);
406 if (err != INSTPROXY_E_SUCCESS) {
407 fprintf(stderr, "ERROR: instproxy_browse returned %d\n", err);
408 goto leave_cleanup;
409 }
410 if (!apps || (plist_get_node_type(apps) != PLIST_ARRAY)) {
411 fprintf(stderr,
412 "ERROR: instproxy_browse returnd an invalid plist!\n");
413 goto leave_cleanup;
414 }
415 if (xml_mode) {
416 char *xml = NULL;
417 uint32_t len = 0;
418
419 plist_to_xml(apps, &xml, &len);
420 if (xml) {
421 puts(xml);
422 free(xml);
423 }
424 plist_free(apps);
425 goto leave_cleanup;
426 }
427 printf("Total: %d apps\n", plist_array_get_size(apps));
428 uint32_t i = 0;
429 for (i = 0; i < plist_array_get_size(apps); i++) {
430 plist_t app = plist_array_get_item(apps, i);
431 plist_t p_appid =
432 plist_dict_get_item(app, "CFBundleIdentifier");
433 char *s_appid = NULL;
434 char *s_dispName = NULL;
435 char *s_version = NULL;
436 plist_t dispName =
437 plist_dict_get_item(app, "CFBundleDisplayName");
438 plist_t version = plist_dict_get_item(app, "CFBundleVersion");
439
440 if (p_appid) {
441 plist_get_string_val(p_appid, &s_appid);
442 }
443 if (!s_appid) {
444 fprintf(stderr, "ERROR: Failed to get APPID!\n");
445 break;
446 }
447
448 if (dispName) {
449 plist_get_string_val(dispName, &s_dispName);
450 }
451 if (version) {
452 plist_get_string_val(version, &s_version);
453 }
454
455 if (!s_dispName) {
456 s_dispName = strdup(s_appid);
457 }
458 if (s_version) {
459 printf("%s - %s %s\n", s_appid, s_dispName, s_version);
460 free(s_version);
461 } else {
462 printf("%s - %s\n", s_appid, s_dispName);
463 }
464 free(s_dispName);
465 free(s_appid);
466 }
467 plist_free(apps);
468 } else if (install_mode || upgrade_mode) {
469 plist_t sinf = NULL;
470 plist_t meta = NULL;
471 char *pkgname = NULL;
472 struct stat fst;
473 FILE *f = NULL;
474 uint64_t af = 0;
475 char buf[8192];
476
477 port = 0;
478 if ((lockdownd_start_service(client, "com.apple.afc", &port) !=
479 LOCKDOWN_E_SUCCESS) || !port) {
480 fprintf(stderr, "Could not start com.apple.afc!\n");
481 goto leave_cleanup;
482 }
483
484 lockdownd_client_free(client);
485 client = NULL;
486
487 if (afc_client_new(phone, port, &afc) != INSTPROXY_E_SUCCESS) {
488 fprintf(stderr, "Could not connect to AFC!\n");
489 goto leave_cleanup;
490 }
491
492 if (stat(appid, &fst) != 0) {
493 fprintf(stderr, "ERROR: stat: %s: %s\n", appid, strerror(errno));
494 goto leave_cleanup;
495 }
496
497 /* open install package */
498 int errp = 0;
499 struct zip *zf = zip_open(appid, 0, &errp);
500 if (!zf) {
501 fprintf(stderr, "ERROR: zip_open: %s: %d\n", appid, errp);
502 goto leave_cleanup;
503 }
504
505 /* extract iTunesMetadata.plist from package */
506 char *zbuf = NULL;
507 uint32_t len = 0;
508 if (zip_f_get_contents(zf, "iTunesMetadata.plist", 0, &zbuf, &len) < 0) {
509 zip_unchange_all(zf);
510 zip_close(zf);
511 goto leave_cleanup;
512 }
513 meta = plist_new_data(zbuf, len);
514 free(zbuf);
515
516 /* we need to get the CFBundleName first */
517 plist_t info = NULL;
518 zbuf = NULL;
519 len = 0;
520 if (zip_f_get_contents(zf, "Info.plist", ZIP_FL_NODIR, &zbuf, &len) < 0) {
521 zip_unchange_all(zf);
522 zip_close(zf);
523 goto leave_cleanup;
524 }
525 if (memcmp(zbuf, "bplist00", 8) == 0) {
526 plist_from_bin(zbuf, len, &info);
527 } else {
528 plist_from_xml(zbuf, len, &info);
529 }
530 free(zbuf);
531
532 if (!info) {
533 fprintf(stderr, "Could not parse Info.plist!\n");
534 zip_unchange_all(zf);
535 zip_close(zf);
536 goto leave_cleanup;
537 }
538
539 char *bundlename = NULL;
540
541 plist_t bname = plist_dict_get_item(info, "CFBundleName");
542 if (bname) {
543 plist_get_string_val(bname, &bundlename);
544 }
545 plist_free(info);
546
547 if (!bundlename) {
548 fprintf(stderr, "Could not determine CFBundleName!\n");
549 zip_unchange_all(zf);
550 zip_close(zf);
551 goto leave_cleanup;
552 }
553
554 char *sinfname = NULL;
555 if (asprintf(&sinfname, "Payload/%s.app/SC_Info/%s.sinf", bundlename, bundlename) < 0) {
556 fprintf(stderr, "Out of memory!?\n");
557 goto leave_cleanup;
558 }
559 free(bundlename);
560
561 /* extract .sinf from package */
562 zbuf = NULL;
563 len = 0;
564 if (zip_f_get_contents(zf, sinfname, 0, &zbuf, &len) == 0) {
565 sinf = plist_new_data(zbuf, len);
566 }
567 free(sinfname);
568 if (zbuf) {
569 free(zbuf);
570 }
571
572 zip_unchange_all(zf);
573 zip_close(zf);
574
575 /* copy archive to device */
576 f = fopen(appid, "r");
577 if (!f) {
578 fprintf(stderr, "fopen: %s: %s\n", appid, strerror(errno));
579 goto leave_cleanup;
580 }
581
582 pkgname = NULL;
583 if (asprintf(&pkgname, "%s/%s", PKG_PATH, basename(appid)) < 0) {
584 fprintf(stderr, "Out of memory!?\n");
585 goto leave_cleanup;
586 }
587
588 printf("Copying '%s' --> '%s'\n", appid, pkgname);
589
590 char **strs = NULL;
591 if (afc_get_file_info(afc, PKG_PATH, &strs) != AFC_E_SUCCESS) {
592 if (afc_make_directory(afc, PKG_PATH) != AFC_E_SUCCESS) {
593 fprintf(stderr, "WARNING: Could not create directory '%s' on device!\n", PKG_PATH);
594 }
595 }
596 if (strs) {
597 int i = 0;
598 while (strs[i]) {
599 free(strs[i]);
600 i++;
601 }
602 free(strs);
603 }
604
605 if ((afc_file_open(afc, pkgname, AFC_FOPEN_WRONLY, &af) !=
606 AFC_E_SUCCESS) || !af) {
607 fclose(f);
608 fprintf(stderr, "afc_file_open on '%s' failed!\n", pkgname);
609 free(pkgname);
610 goto leave_cleanup;
611 }
612
613 size_t amount = 0;
614 do {
615 amount = fread(buf, 1, sizeof(buf), f);
616 if (amount > 0) {
617 uint32_t written, total = 0;
618 while (total < amount) {
619 written = 0;
620 if (afc_file_write(afc, af, buf, amount, &written) !=
621 AFC_E_SUCCESS) {
622 fprintf(stderr, "AFC Write error!\n");
623 break;
624 }
625 total += written;
626 }
627 if (total != amount) {
628 fprintf(stderr, "Error: wrote only %d of %d\n", total,
629 amount);
630 afc_file_close(afc, af);
631 fclose(f);
632 free(pkgname);
633 goto leave_cleanup;
634 }
635 }
636 }
637 while (amount > 0);
638
639 afc_file_close(afc, af);
640 fclose(f);
641
642 printf("done.\n");
643
644 /* perform installation or upgrade */
645 if (install_mode) {
646 printf("Installing '%s'\n", pkgname);
647 instproxy_install(ipc, pkgname, sinf, meta, status_cb);
648 } else {
649 printf("Upgrading '%s'\n", pkgname);
650 instproxy_upgrade(ipc, pkgname, sinf, meta, status_cb);
651 }
652 free(pkgname);
653 wait_for_op_complete = 1;
654 notification_expected = 1;
655 } else if (uninstall_mode) {
656 instproxy_uninstall(ipc, appid, status_cb);
657 wait_for_op_complete = 1;
658 notification_expected = 1;
659 } else if (list_archives_mode) {
660 int xml_mode = 0;
661 plist_t dict = NULL;
662 plist_t lres = NULL;
663 instproxy_error_t err;
664
665 /* look for options */
666 if (options) {
667 char *opts = strdup(options);
668 char *elem = strtok(opts, ",");
669 while (elem) {
670 if (!strcmp(elem, "xml")) {
671 xml_mode = 1;
672 }
673 elem = strtok(NULL, ",");
674 }
675 }
676
677 err = instproxy_lookup_archives(ipc, &dict);
678 if (err != INSTPROXY_E_SUCCESS) {
679 fprintf(stderr, "ERROR: lookup_archives returned %d\n", err);
680 goto leave_cleanup;
681 }
682 if (!dict) {
683 fprintf(stderr,
684 "ERROR: lookup_archives did not return a plist!?\n");
685 goto leave_cleanup;
686 }
687
688 lres = plist_dict_get_item(dict, "LookupResult");
689 if (!lres || (plist_get_node_type(lres) != PLIST_DICT)) {
690 plist_free(dict);
691 fprintf(stderr, "ERROR: Could not get dict 'LookupResult'\n");
692 goto leave_cleanup;
693 }
694
695 if (xml_mode) {
696 char *xml = NULL;
697 uint32_t len = 0;
698
699 plist_to_xml(lres, &xml, &len);
700 if (xml) {
701 puts(xml);
702 free(xml);
703 }
704 plist_free(dict);
705 goto leave_cleanup;
706 }
707 plist_dict_iter iter = NULL;
708 plist_t node = NULL;
709 char *key = NULL;
710
711 printf("Total: %d archived apps\n", plist_dict_get_size(lres));
712 plist_dict_new_iter(lres, &iter);
713 if (!iter) {
714 plist_free(dict);
715 fprintf(stderr, "ERROR: Could not create plist_dict_iter!\n");
716 goto leave_cleanup;
717 }
718 do {
719 key = NULL;
720 node = NULL;
721 plist_dict_next_item(lres, iter, &key, &node);
722 if (key && (plist_get_node_type(node) == PLIST_DICT)) {
723 char *s_dispName = NULL;
724 char *s_version = NULL;
725 plist_t dispName =
726 plist_dict_get_item(node, "CFBundleDisplayName");
727 plist_t version =
728 plist_dict_get_item(node, "CFBundleVersion");
729 if (dispName) {
730 plist_get_string_val(dispName, &s_dispName);
731 }
732 if (version) {
733 plist_get_string_val(version, &s_version);
734 }
735 if (!s_dispName) {
736 s_dispName = strdup(key);
737 }
738 if (s_version) {
739 printf("%s - %s %s\n", key, s_dispName, s_version);
740 free(s_version);
741 } else {
742 printf("%s - %s\n", key, s_dispName);
743 }
744 free(s_dispName);
745 free(key);
746 }
747 }
748 while (node);
749 plist_free(dict);
750 } else if (archive_mode) {
751 uint32_t opt = INSTPROXY_ARCHIVE_SKIP_UNINSTALL;
752 char *copy_path = NULL;
753 int remove_after_copy = 0;
754 /* look for options */
755 if (options) {
756 char *opts = strdup(options);
757 char *elem = strtok(opts, ",");
758 while (elem) {
759 if (!strcmp(elem, "uninstall")) {
760 opt &= ~INSTPROXY_ARCHIVE_SKIP_UNINSTALL;
761 } else if (!strcmp(elem, "app_only")) {
762 opt |= INSTPROXY_ARCHIVE_APP_ONLY;
763 } else if ((strlen(elem) > 5) && !strncmp(elem, "copy=", 5)) {
764 copy_path = strdup(elem+5);
765 } else if (!strcmp(elem, "remove")) {
766 remove_after_copy = 1;
767 }
768 elem = strtok(NULL, ",");
769 }
770 }
771
772 if (copy_path) {
773 struct stat fst;
774 if (stat(copy_path, &fst) != 0) {
775 fprintf(stderr, "ERROR: stat: %s: %s\n", copy_path, strerror(errno));
776 free(copy_path);
777 goto leave_cleanup;
778 }
779
780 if (!S_ISDIR(fst.st_mode)) {
781 fprintf(stderr, "ERROR: '%s' is not a directory as expected.\n", copy_path);
782 free(copy_path);
783 goto leave_cleanup;
784 }
785
786 port = 0;
787 if ((lockdownd_start_service(client, "com.apple.afc", &port) != LOCKDOWN_E_SUCCESS) || !port) {
788 fprintf(stderr, "Could not start com.apple.afc!\n");
789 free(copy_path);
790 goto leave_cleanup;
791 }
792
793 lockdownd_client_free(client);
794 client = NULL;
795
796 if (afc_client_new(phone, port, &afc) != INSTPROXY_E_SUCCESS) {
797 fprintf(stderr, "Could not connect to AFC!\n");
798 goto leave_cleanup;
799 }
800 }
801
802 instproxy_archive(ipc, appid, opt, status_cb);
803 wait_for_op_complete = 1;
804 if (opt & INSTPROXY_ARCHIVE_SKIP_UNINSTALL) {
805 notification_expected = 0;
806 } else {
807 notification_expected = 1;
808 }
809
810 do_wait_when_needed();
811
812 if (copy_path) {
813 if (err_occured) {
814 afc_client_free(afc);
815 afc = NULL;
816 goto leave_cleanup;
817 }
818 FILE *f = NULL;
819 uint64_t af = 0;
820 /* local filename */
821 char *localfile = NULL;
822 if (asprintf(&localfile, "%s/%s.ipa", copy_path, appid) < 0) {
823 fprintf(stderr, "Out of memory!?\n");
824 goto leave_cleanup;
825 }
826 free(copy_path);
827
828 f = fopen(localfile, "w");
829 if (!f) {
830 fprintf(stderr, "ERROR: fopen: %s: %s\n", localfile, strerror(errno));
831 free(localfile);
832 goto leave_cleanup;
833 }
834
835 /* remote filename */
836 char *remotefile = NULL;
837 if (asprintf(&remotefile, "%s/%s.zip", APPARCH_PATH, appid) < 0) {
838 fprintf(stderr, "Out of memory!?\n");
839 goto leave_cleanup;
840 }
841
842 uint32_t fsize = 0;
843 char **fileinfo = NULL;
844 if ((afc_get_file_info(afc, remotefile, &fileinfo) != AFC_E_SUCCESS) || !fileinfo) {
845 fprintf(stderr, "ERROR getting AFC file info for '%s' on device!\n", remotefile);
846 fclose(f);
847 free(remotefile);
848 free(localfile);
849 goto leave_cleanup;
850 }
851
852 int i;
853 for (i = 0; fileinfo[i]; i+=2) {
854 if (!strcmp(fileinfo[i], "st_size")) {
855 fsize = atoi(fileinfo[i+1]);
856 break;
857 }
858 }
859 i = 0;
860 while (fileinfo[i]) {
861 free(fileinfo[i]);
862 i++;
863 }
864 free(fileinfo);
865
866 if (fsize == 0) {
867 fprintf(stderr, "Hm... remote file length could not be determined. Cannot copy.\n");
868 fclose(f);
869 free(remotefile);
870 free(localfile);
871 goto leave_cleanup;
872 }
873
874 if ((afc_file_open(afc, remotefile, AFC_FOPEN_RDONLY, &af) != AFC_E_SUCCESS) || !af) {
875 fclose(f);
876 fprintf(stderr, "ERROR: could not open '%s' on device for reading!\n", remotefile);
877 free(remotefile);
878 free(localfile);
879 goto leave_cleanup;
880 }
881
882 /* copy file over */
883 printf("Copying '%s' --> '%s'\n", remotefile, localfile);
884 free(remotefile);
885 free(localfile);
886
887 uint32_t amount = 0;
888 uint32_t total = 0;
889 char buf[8192];
890
891 do {
892 if (afc_file_read(afc, af, buf, sizeof(buf), &amount) != AFC_E_SUCCESS) {
893 fprintf(stderr, "AFC Read error!\n");
894 break;
895 }
896
897 if (amount > 0) {
898 size_t written = fwrite(buf, 1, amount, f);
899 if (written != amount) {
900 fprintf(stderr, "Error when writing %d bytes to local file!\n", amount);
901 break;
902 }
903 total += written;
904 }
905 } while (amount > 0);
906
907 afc_file_close(afc, af);
908 fclose(f);
909
910 printf("done.\n");
911 if (total != fsize) {
912 fprintf(stderr, "WARNING: remote and local file sizes don't match (%d != %d)\n", fsize, total);
913 if (remove_after_copy) {
914 fprintf(stderr, "NOTE: archive file will NOT be removed from device\n");
915 remove_after_copy = 0;
916 }
917 }
918
919 if (remove_after_copy) {
920 /* remove archive if requested */
921 printf("Removing '%s'\n", appid);
922 archive_mode = 0;
923 remove_archive_mode = 1;
924 free(options);
925 options = NULL;
926 if (LOCKDOWN_E_SUCCESS != lockdownd_client_new(phone, &client)) {
927 fprintf(stderr, "Could not connect to lockdownd. Exiting.\n");
928 goto leave_cleanup;
929 }
930 goto run_again;
931 }
932 }
933 goto leave_cleanup;
934 } else if (restore_mode) {
935 instproxy_restore(ipc, appid, status_cb);
936 wait_for_op_complete = 1;
937 notification_expected = 1;
938 } else if (remove_archive_mode) {
939 instproxy_remove_archive(ipc, appid, status_cb);
940 wait_for_op_complete = 1;
941 } else {
942 printf
943 ("ERROR: no operation selected?! This should not be reached!\n");
944 res = -2;
945 goto leave_cleanup;
946 }
947
948 if (client) {
949 /* not needed anymore */
950 lockdownd_client_free(client);
951 client = NULL;
952 }
953
954 do_wait_when_needed();
955
956 leave_cleanup:
957 if (np) {
958 np_client_free(np);
959 }
960 if (ipc) {
961 instproxy_client_free(ipc);
962 }
963 if (afc) {
964 afc_client_free(afc);
965 }
966 if (client) {
967 lockdownd_client_free(client);
968 }
969 iphone_device_free(phone);
970
971 if (uuid) {
972 free(uuid);
973 }
974 if (appid) {
975 free(appid);
976 }
977 if (options) {
978 free(options);
979 }
980
981 return res;
982}