summaryrefslogtreecommitdiffstats
path: root/tools/ideviceprovision.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/ideviceprovision.c')
-rw-r--r--tools/ideviceprovision.c689
1 files changed, 689 insertions, 0 deletions
diff --git a/tools/ideviceprovision.c b/tools/ideviceprovision.c
new file mode 100644
index 0000000..4080a28
--- /dev/null
+++ b/tools/ideviceprovision.c
@@ -0,0 +1,689 @@
1/*
2 * ideviceprovision.c
3 * Simple utility to install, get, or remove provisioning profiles
4 * to/from idevices
5 *
6 * Copyright (c) 2012-2016 Nikias Bassen, All Rights Reserved.
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library 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 GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22
23#ifdef HAVE_CONFIG_H
24#include <config.h>
25#endif
26
27#define TOOL_NAME "ideviceprovision"
28
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <getopt.h>
33#include <sys/stat.h>
34#include <errno.h>
35#ifndef WIN32
36#include <signal.h>
37#endif
38
39#ifdef WIN32
40#include <windows.h>
41#else
42#include <arpa/inet.h>
43#endif
44
45#include <libimobiledevice/libimobiledevice.h>
46#include <libimobiledevice/lockdown.h>
47#include <libimobiledevice/misagent.h>
48#include <plist/plist.h>
49
50static void print_usage(int argc, char **argv, int is_error)
51{
52 char *name = strrchr(argv[0], '/');
53 fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] COMMAND\n", (name ? name + 1: argv[0]));
54 fprintf(is_error ? stderr : stdout,
55 "\n"
56 "Manage provisioning profiles on a device.\n"
57 "\n"
58 "Where COMMAND is one of:\n"
59 " install FILE Installs the provisioning profile specified by FILE.\n"
60 " A valid .mobileprovision file is expected.\n"
61 " list Get a list of all provisioning profiles on the device.\n"
62 " copy PATH Retrieves all provisioning profiles from the device and\n"
63 " stores them into the existing directory specified by PATH.\n"
64 " The files will be stored as UUID.mobileprovision\n"
65 " copy UUID PATH Retrieves the provisioning profile identified by UUID\n"
66 " from the device and stores it into the existing directory\n"
67 " specified by PATH. The file will be stored as UUID.mobileprovision.\n"
68 " remove UUID Removes the provisioning profile identified by UUID.\n"
69 " remove-all Removes all installed provisioning profiles.\n"
70 " dump FILE Prints detailed information about the provisioning profile\n"
71 " specified by FILE.\n"
72 "\n"
73 "The following OPTIONS are accepted:\n"
74 " -u, --udid UDID target specific device by UDID\n"
75 " -n, --network connect to network device\n"
76 " -x, --xml print XML output when using the 'dump' command\n"
77 " -d, --debug enable communication debugging\n"
78 " -h, --help prints usage information\n"
79 " -v, --version prints version information\n"
80 "\n"
81 "Homepage: <" PACKAGE_URL ">\n"
82 "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
83 );
84}
85
86enum {
87 OP_INSTALL,
88 OP_LIST,
89 OP_COPY,
90 OP_REMOVE,
91 OP_DUMP,
92 NUM_OPS
93};
94
95#define ASN1_SEQUENCE 0x30
96#define ASN1_CONTAINER 0xA0
97#define ASN1_OBJECT_IDENTIFIER 0x06
98#define ASN1_OCTET_STRING 0x04
99
100static void asn1_next_item(unsigned char** p)
101{
102 char bsize = *(*p+1);
103 if (bsize & 0x80) {
104 *p += 2 + (bsize & 0xF);
105 } else {
106 *p += 3;
107 }
108}
109
110static size_t asn1_item_get_size(const unsigned char* p)
111{
112 size_t res = 0;
113 char bsize = *(p+1);
114 if (bsize & 0x80) {
115 uint16_t ws = 0;
116 uint32_t ds = 0;
117 switch (bsize & 0xF) {
118 case 2:
119 ws = *(uint16_t*)(p+2);
120 res = ntohs(ws);
121 break;
122 case 3:
123 ds = *(uint32_t*)(p+2);
124 res = ntohl(ds) >> 8;
125 break;
126 case 4:
127 ds = *(uint32_t*)(p+2);
128 res = ntohl(ds);
129 break;
130 default:
131 fprintf(stderr, "ERROR: Invalid or unimplemented byte size %d\n", bsize & 0xF);
132 break;
133 }
134 } else {
135 res = (int)bsize;
136 }
137 return res;
138}
139
140static void asn1_skip_item(unsigned char** p)
141{
142 size_t sz = asn1_item_get_size(*p);
143 *p += 2;
144 *p += sz;
145}
146
147static plist_t profile_get_embedded_plist(plist_t profile)
148{
149 if (plist_get_node_type(profile) != PLIST_DATA) {
150 fprintf(stderr, "%s: unexpected plist node type for profile (PLIST_DATA expected)\n", __func__);
151 return NULL;
152 }
153 char* bbuf = NULL;
154 uint64_t blen = 0;
155 plist_get_data_val(profile, &bbuf, &blen);
156 if (!bbuf) {
157 fprintf(stderr, "%s: could not get data value from plist node\n", __func__);
158 return NULL;
159 }
160
161 unsigned char* pp = (unsigned char*)bbuf;
162
163 if (*pp != ASN1_SEQUENCE) {
164 free(bbuf);
165 fprintf(stderr, "%s: unexpected profile data (0)\n", __func__);
166 return NULL;
167 }
168 size_t slen = asn1_item_get_size(pp);
169 char bsize = *(pp+1);
170 if (bsize & 0x80) {
171 slen += 2 + (bsize & 0xF);
172 } else {
173 slen += 3;
174 }
175 if (slen != blen) {
176 free(bbuf);
177 fprintf(stderr, "%s: unexpected profile data (1)\n", __func__);
178 return NULL;
179 }
180 asn1_next_item(&pp);
181
182 if (*pp != ASN1_OBJECT_IDENTIFIER) {
183 free(bbuf);
184 fprintf(stderr, "%s: unexpected profile data (2)\n", __func__);
185 return NULL;
186 }
187 asn1_skip_item(&pp);
188
189 if (*pp != ASN1_CONTAINER) {
190 free(bbuf);
191 fprintf(stderr, "%s: unexpected profile data (3)\n", __func__);
192 return NULL;
193 }
194 asn1_next_item(&pp);
195
196 if (*pp != ASN1_SEQUENCE) {
197 free(bbuf);
198 fprintf(stderr, "%s: unexpected profile data (4)\n", __func__);
199 return NULL;
200 }
201 asn1_next_item(&pp);
202
203 int k = 0;
204 // go to the 3rd element (skip 2)
205 while (k < 2) {
206 asn1_skip_item(&pp);
207 k++;
208 }
209 if (*pp != ASN1_SEQUENCE) {
210 free(bbuf);
211 fprintf(stderr, "%s: unexpected profile data (5)\n", __func__);
212 return NULL;
213 }
214 asn1_next_item(&pp);
215
216 if (*pp != ASN1_OBJECT_IDENTIFIER) {
217 free(bbuf);
218 fprintf(stderr, "%s: unexpected profile data (6)\n", __func__);
219 return NULL;
220 }
221 asn1_skip_item(&pp);
222
223 if (*pp != ASN1_CONTAINER) {
224 free(bbuf);
225 fprintf(stderr, "%s: unexpected profile data (7)\n", __func__);
226 return NULL;
227 }
228 asn1_next_item(&pp);
229
230 if (*pp != ASN1_OCTET_STRING) {
231 free(bbuf);
232 fprintf(stderr, "%s: unexpected profile data (8)\n", __func__);
233 return NULL;
234 }
235 slen = asn1_item_get_size(pp);
236 asn1_next_item(&pp);
237
238 plist_t pl = NULL;
239 plist_from_xml((char*)pp, slen, &pl);
240 free(bbuf);
241
242 return pl;
243}
244
245static int profile_read_from_file(const char* path, unsigned char **profile_data, unsigned int *profile_size)
246{
247 FILE* f = fopen(path, "rb");
248 if (!f) {
249 fprintf(stderr, "Could not open file '%s'\n", path);
250 return -1;
251 }
252 fseek(f, 0, SEEK_END);
253 long int size = ftell(f);
254 fseek(f, 0, SEEK_SET);
255
256 if (size >= 0x1000000) {
257 fprintf(stderr, "The file '%s' is too large for processing.\n", path);
258 fclose(f);
259 return -1;
260 }
261
262 unsigned char* buf = malloc(size);
263 if (!buf) {
264 fprintf(stderr, "Could not allocate memory...\n");
265 fclose(f);
266 return -1;
267 }
268
269 long int cur = 0;
270 while (cur < size) {
271 ssize_t r = fread(buf+cur, 1, 512, f);
272 if (r <= 0) {
273 break;
274 }
275 cur += r;
276 }
277 fclose(f);
278
279 if (cur != size) {
280 free(buf);
281 fprintf(stderr, "Could not read in file '%s' (size %ld read %ld)\n", path, size, cur);
282 return -1;
283 }
284
285 *profile_data = buf;
286 *profile_size = (unsigned int)size;
287
288 return 0;
289}
290
291int main(int argc, char *argv[])
292{
293 lockdownd_client_t client = NULL;
294 lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR;
295 lockdownd_service_descriptor_t service = NULL;
296 idevice_t device = NULL;
297 idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR;
298 int res = 0;
299 int i;
300 int op = -1;
301 int output_xml = 0;
302 const char* udid = NULL;
303 const char* param = NULL;
304 const char* param2 = NULL;
305 int use_network = 0;
306 int c = 0;
307 const struct option longopts[] = {
308 { "debug", no_argument, NULL, 'd' },
309 { "help", no_argument, NULL, 'h' },
310 { "udid", required_argument, NULL, 'u' },
311 { "network", no_argument, NULL, 'n' },
312 { "version", no_argument, NULL, 'v' },
313 { "xml", no_argument, NULL, 'x' },
314 { NULL, 0, NULL, 0}
315 };
316
317#ifndef WIN32
318 signal(SIGPIPE, SIG_IGN);
319#endif
320 /* parse cmdline args */
321 while ((c = getopt_long(argc, argv, "dhu:nvx", longopts, NULL)) != -1) {
322 switch (c) {
323 case 'd':
324 idevice_set_debug_level(1);
325 break;
326 case 'u':
327 if (!*optarg) {
328 fprintf(stderr, "ERROR: UDID argument must not be empty!\n");
329 print_usage(argc, argv, 1);
330 return 2;
331 }
332 udid = optarg;
333 break;
334 case 'n':
335 use_network = 1;
336 break;
337 case 'h':
338 print_usage(argc, argv, 0);
339 return 0;
340 case 'v':
341 printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
342 return 0;
343 case 'x':
344 output_xml = 1;
345 break;
346 default:
347 print_usage(argc, argv, 1);
348 return 2;
349 }
350 }
351 argc -= optind;
352 argv += optind;
353
354 if (!argv[0]) {
355 fprintf(stderr, "ERROR: Missing command.\n");
356 print_usage(argc+optind, argv-optind, 1);
357 return 2;
358 }
359
360 i = 0;
361 if (!strcmp(argv[i], "install")) {
362 op = OP_INSTALL;
363 i++;
364 if (!argv[i] || !*argv[i]) {
365 fprintf(stderr, "Missing argument for 'install' command.\n");
366 print_usage(argc+optind, argv-optind, 1);
367 return 2;
368 }
369 param = argv[i];
370 }
371 else if (!strcmp(argv[i], "list")) {
372 op = OP_LIST;
373 }
374 else if (!strcmp(argv[i], "copy")) {
375 op = OP_COPY;
376 i++;
377 if (!argv[i] || !*argv[i]) {
378 fprintf(stderr, "Missing argument for 'copy' command.\n");
379 print_usage(argc+optind, argv-optind, 1);
380 return 2;
381 }
382 param = argv[i];
383 i++;
384 if (argv[i] && (strlen(argv[i]) > 0)) {
385 param2 = argv[i];
386 }
387 }
388 else if (!strcmp(argv[i], "remove")) {
389 op = OP_REMOVE;
390 i++;
391 if (!argv[i] || !*argv[i]) {
392 fprintf(stderr, "Missing argument for 'remove' command.\n");
393 print_usage(argc+optind, argv-optind, 1);
394 return 2;
395 }
396 param = argv[i];
397 }
398 else if (!strcmp(argv[i], "remove-all")) {
399 op = OP_REMOVE;
400 }
401 else if (!strcmp(argv[i], "dump")) {
402 op = OP_DUMP;
403 i++;
404 if (!argv[i] || !*argv[i]) {
405 fprintf(stderr, "Missing argument for 'remove' command.\n");
406 print_usage(argc+optind, argv-optind, 1);
407 return 2;
408 }
409 param = argv[i];
410 }
411 if ((op == -1) || (op >= NUM_OPS)) {
412 fprintf(stderr, "ERROR: Unsupported command '%s'\n", argv[i]);
413 print_usage(argc+optind, argv-optind, 1);
414 return 2;
415 }
416
417 if (op == OP_DUMP) {
418 unsigned char* profile_data = NULL;
419 unsigned int profile_size = 0;
420 if (profile_read_from_file(param, &profile_data, &profile_size) != 0) {
421 return -1;
422 }
423 plist_t pdata = plist_new_data((char*)profile_data, profile_size);
424 plist_t pl = profile_get_embedded_plist(pdata);
425 plist_free(pdata);
426 free(profile_data);
427
428 if (pl) {
429 if (output_xml) {
430 char* xml = NULL;
431 uint32_t xlen = 0;
432 plist_to_xml(pl, &xml, &xlen);
433 if (xml) {
434 printf("%s\n", xml);
435 free(xml);
436 }
437 } else {
438 if (pl && (plist_get_node_type(pl) == PLIST_DICT)) {
439 plist_write_to_stream(pl, stdout, PLIST_FORMAT_LIMD, 0);
440 } else {
441 fprintf(stderr, "ERROR: unexpected node type in profile plist (not PLIST_DICT)\n");
442 res = -1;
443 }
444 }
445 } else {
446 fprintf(stderr, "ERROR: could not extract embedded plist from profile!\n");
447 }
448 plist_free(pl);
449
450 return res;
451 }
452
453 if (op == OP_COPY) {
454 struct stat st;
455 const char *checkdir = (param2) ? param2 : param;
456 if ((stat(checkdir, &st) < 0) || !S_ISDIR(st.st_mode)) {
457 fprintf(stderr, "ERROR: %s does not exist or is not a directory!\n", checkdir);
458 return -1;
459 }
460 }
461
462 ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX);
463 if (ret != IDEVICE_E_SUCCESS) {
464 if (udid) {
465 printf("No device found with udid %s.\n", udid);
466 } else {
467 printf("No device found.\n");
468 }
469 return -1;
470 }
471
472 if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &client, TOOL_NAME))) {
473 fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", ldret);
474 idevice_free(device);
475 return -1;
476 }
477
478 plist_t pver = NULL;
479 char *pver_s = NULL;
480 lockdownd_get_value(client, NULL, "ProductVersion", &pver);
481 if (pver && plist_get_node_type(pver) == PLIST_STRING) {
482 plist_get_string_val(pver, &pver_s);
483 }
484 plist_free(pver);
485 int product_version_major = 0;
486 int product_version_minor = 0;
487 int product_version_patch = 0;
488 if (pver_s) {
489 sscanf(pver_s, "%d.%d.%d", &product_version_major, &product_version_minor, &product_version_patch);
490 free(pver_s);
491 }
492 if (product_version_major == 0) {
493 fprintf(stderr, "ERROR: Could not determine the device's ProductVersion\n");
494 lockdownd_client_free(client);
495 idevice_free(device);
496 return -1;
497 }
498 int product_version = ((product_version_major & 0xFF) << 16) | ((product_version_minor & 0xFF) << 8) | (product_version_patch & 0xFF);
499
500 lockdownd_error_t lerr = lockdownd_start_service(client, MISAGENT_SERVICE_NAME, &service);
501 if (lerr != LOCKDOWN_E_SUCCESS) {
502 fprintf(stderr, "Could not start service %s: %s\n", MISAGENT_SERVICE_NAME, lockdownd_strerror(lerr));
503 lockdownd_client_free(client);
504 idevice_free(device);
505 return -1;
506 }
507 lockdownd_client_free(client);
508 client = NULL;
509
510 misagent_client_t mis = NULL;
511 if (misagent_client_new(device, service, &mis) != MISAGENT_E_SUCCESS) {
512 fprintf(stderr, "Could not connect to %s on device\n", MISAGENT_SERVICE_NAME);
513 if (service)
514 lockdownd_service_descriptor_free(service);
515 lockdownd_client_free(client);
516 idevice_free(device);
517 return -1;
518 }
519
520 if (service)
521 lockdownd_service_descriptor_free(service);
522
523 switch (op) {
524 case OP_INSTALL:
525 {
526 unsigned char* profile_data = NULL;
527 unsigned int profile_size = 0;
528 if (profile_read_from_file(param, &profile_data, &profile_size) != 0) {
529 break;
530 }
531
532 uint64_t psize = profile_size;
533 plist_t pdata = plist_new_data((const char*)profile_data, psize);
534 free(profile_data);
535
536 if (misagent_install(mis, pdata) == MISAGENT_E_SUCCESS) {
537 printf("Profile '%s' installed successfully.\n", param);
538 } else {
539 int sc = misagent_get_status_code(mis);
540 fprintf(stderr, "Could not install profile '%s', status code: 0x%x\n", param, sc);
541 }
542 }
543 break;
544 case OP_LIST:
545 case OP_COPY:
546 {
547 plist_t profiles = NULL;
548 misagent_error_t merr;
549 if (product_version < 0x090300) {
550 merr = misagent_copy(mis, &profiles);
551 } else {
552 merr = misagent_copy_all(mis, &profiles);
553 }
554 if (merr == MISAGENT_E_SUCCESS) {
555 int found_match = 0;
556 uint32_t num_profiles = plist_array_get_size(profiles);
557 if (op == OP_LIST || !param2) {
558 printf("Device has %d provisioning %s installed:\n", num_profiles, (num_profiles == 1) ? "profile" : "profiles");
559 }
560 uint32_t j;
561 for (j = 0; !found_match && j < num_profiles; j++) {
562 char* p_name = NULL;
563 char* p_uuid = NULL;
564 plist_t profile = plist_array_get_item(profiles, j);
565 plist_t pl = profile_get_embedded_plist(profile);
566 if (pl && (plist_get_node_type(pl) == PLIST_DICT)) {
567 plist_t node;
568 node = plist_dict_get_item(pl, "Name");
569 if (node && (plist_get_node_type(node) == PLIST_STRING)) {
570 plist_get_string_val(node, &p_name);
571 }
572 node = plist_dict_get_item(pl, "UUID");
573 if (node && (plist_get_node_type(node) == PLIST_STRING)) {
574 plist_get_string_val(node, &p_uuid);
575 }
576 }
577 if (param2) {
578 if (p_uuid && !strcmp(p_uuid, param)) {
579 found_match = 1;
580 } else {
581 free(p_uuid);
582 free(p_name);
583 continue;
584 }
585 }
586 printf("%s - %s\n", (p_uuid) ? p_uuid : "(unknown id)", (p_name) ? p_name : "(no name)");
587 if (op == OP_COPY) {
588 char pfname[512];
589 if (p_uuid) {
590 sprintf(pfname, "%s/%s.mobileprovision", (param2) ? param2 : param, p_uuid);
591 } else {
592 sprintf(pfname, "%s/profile%d.mobileprovision", (param2) ? param2 : param, j);
593 }
594 FILE* f = fopen(pfname, "wb");
595 if (f) {
596 char* dt = NULL;
597 uint64_t ds = 0;
598 plist_get_data_val(profile, &dt, &ds);
599 fwrite(dt, 1, ds, f);
600 fclose(f);
601 printf(" => %s\n", pfname);
602 } else {
603 fprintf(stderr, "Could not open '%s' for writing: %s\n", pfname, strerror(errno));
604 }
605 }
606 free(p_uuid);
607 free(p_name);
608 }
609 if (param2 && !found_match) {
610 fprintf(stderr, "Profile '%s' was not found on the device.\n", param);
611 res = -1;
612 }
613 } else {
614 int sc = misagent_get_status_code(mis);
615 fprintf(stderr, "Could not get installed profiles from device, status code: 0x%x\n", sc);
616 res = -1;
617 }
618 plist_free(profiles);
619 }
620 break;
621 case OP_REMOVE:
622 if (param) {
623 /* remove specified provisioning profile */
624 if (misagent_remove(mis, param) == MISAGENT_E_SUCCESS) {
625 printf("Profile '%s' removed.\n", param);
626 } else {
627 int sc = misagent_get_status_code(mis);
628 fprintf(stderr, "Could not remove profile '%s', status code 0x%x\n", param, sc);
629 }
630 } else {
631 /* remove all provisioning profiles */
632 plist_t profiles = NULL;
633 misagent_error_t merr;
634 if (product_version < 0x090300) {
635 merr = misagent_copy(mis, &profiles);
636 } else {
637 merr = misagent_copy_all(mis, &profiles);
638 }
639 if (merr == MISAGENT_E_SUCCESS) {
640 uint32_t j;
641 uint32_t num_removed = 0;
642 for (j = 0; j < plist_array_get_size(profiles); j++) {
643 char* p_name = NULL;
644 char* p_uuid = NULL;
645 plist_t profile = plist_array_get_item(profiles, j);
646 plist_t pl = profile_get_embedded_plist(profile);
647 if (pl && (plist_get_node_type(pl) == PLIST_DICT)) {
648 plist_t node;
649 node = plist_dict_get_item(pl, "Name");
650 if (node && (plist_get_node_type(node) == PLIST_STRING)) {
651 plist_get_string_val(node, &p_name);
652 }
653 node = plist_dict_get_item(pl, "UUID");
654 if (node && (plist_get_node_type(node) == PLIST_STRING)) {
655 plist_get_string_val(node, &p_uuid);
656 }
657 }
658 if (p_uuid) {
659 if (misagent_remove(mis, p_uuid) == MISAGENT_E_SUCCESS) {
660 printf("OK profile removed: %s - %s\n", p_uuid, (p_name) ? p_name : "(no name)");
661 num_removed++;
662 } else {
663 int sc = misagent_get_status_code(mis);
664 printf("FAIL profile not removed: %s - %s (status code 0x%x)\n", p_uuid, (p_name) ? p_name : "(no name)", sc);
665 }
666 }
667 free(p_name);
668 free(p_uuid);
669 }
670 printf("%d profiles removed.\n", num_removed);
671 } else {
672 int sc = misagent_get_status_code(mis);
673 fprintf(stderr, "Could not get installed profiles from device, status code: 0x%x\n", sc);
674 res = -1;
675 }
676 plist_free(profiles);
677 }
678 break;
679 default:
680 break;
681 }
682
683 misagent_client_free(mis);
684
685 idevice_free(device);
686
687 return res;
688}
689