summaryrefslogtreecommitdiffstats
path: root/tools/idevicedevmodectl.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/idevicedevmodectl.c')
-rw-r--r--tools/idevicedevmodectl.c462
1 files changed, 462 insertions, 0 deletions
diff --git a/tools/idevicedevmodectl.c b/tools/idevicedevmodectl.c
new file mode 100644
index 0000000..bd1de6a
--- /dev/null
+++ b/tools/idevicedevmodectl.c
@@ -0,0 +1,462 @@
1/*
2 * idevicedevmodectl.c
3 * List or enable Developer Mode on iOS 16+ devices
4 *
5 * Copyright (c) 2022 Nikias Bassen, All Rights Reserved.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22#ifdef HAVE_CONFIG_H
23#include <config.h>
24#endif
25
26#define TOOL_NAME "idevicedevmodectl"
27
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <getopt.h>
32#include <sys/stat.h>
33#include <unistd.h>
34#include <errno.h>
35#ifndef WIN32
36#include <signal.h>
37#endif
38
39#ifdef WIN32
40#include <windows.h>
41#define __usleep(x) Sleep(x/1000)
42#else
43#include <arpa/inet.h>
44#include <unistd.h>
45#define __usleep(x) usleep(x)
46#endif
47
48#include <libimobiledevice/libimobiledevice.h>
49#include <libimobiledevice/lockdown.h>
50#include <libimobiledevice/property_list_service.h>
51#include <libimobiledevice-glue/utils.h>
52
53#define AMFI_LOCKDOWN_SERVICE_NAME "com.apple.amfi.lockdown"
54
55static char* udid = NULL;
56static int use_network = 0;
57
58static void print_usage(int argc, char **argv, int is_error)
59{
60 char *name = strrchr(argv[0], '/');
61 fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] COMMAND\n", (name ? name + 1: argv[0]));
62 fprintf(is_error ? stderr : stdout,
63 "\n"
64 "Enable Developer Mode on iOS 16+ devices or print the current status.\n"
65 "\n"
66 "Where COMMAND is one of:\n"
67 " list Print the Developer Mode status of all connected devices\n"
68 " or for a specific one if --udid is given.\n"
69 " enable Enable Developer Mode (device will reboot),\n"
70 " and confirm it after device booted up again.\n"
71 "\n"
72 " arm Arm the Developer Mode (device will reboot)\n"
73 " confirm Confirm enabling of Developer Mode\n"
74 " reveal Reveal the Developer Mode menu on the device\n"
75 "\n"
76 "The following OPTIONS are accepted:\n"
77 " -u, --udid UDID target specific device by UDID\n"
78 " -n, --network connect to network device\n"
79 " -d, --debug enable communication debugging\n"
80 " -h, --help print usage information\n"
81 " -v, --version print version information\n"
82 "\n"
83 "Homepage: <" PACKAGE_URL ">\n"
84 "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
85 );
86}
87
88enum {
89 OP_LIST,
90 OP_ENABLE,
91 OP_ARM,
92 OP_CONFIRM,
93 OP_REVEAL,
94 NUM_OPS
95};
96#define DEV_MODE_REVEAL 0
97#define DEV_MODE_ARM 1
98#define DEV_MODE_ENABLE 2
99
100static int get_developer_mode_status(const char* device_udid, int _use_network)
101{
102 idevice_error_t ret;
103 idevice_t device = NULL;
104 lockdownd_client_t lockdown = NULL;
105 lockdownd_error_t lerr = LOCKDOWN_E_UNKNOWN_ERROR;
106 plist_t val = NULL;
107
108 ret = idevice_new_with_options(&device, device_udid, (_use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX);
109 if (ret != IDEVICE_E_SUCCESS) {
110 return -1;
111 }
112
113 if (LOCKDOWN_E_SUCCESS != (lerr = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) {
114 idevice_free(device);
115 return -1;
116 }
117
118 lerr = lockdownd_get_value(lockdown, "com.apple.security.mac.amfi", "DeveloperModeStatus", &val);
119 if (lerr != LOCKDOWN_E_SUCCESS) {
120 fprintf(stderr, "ERROR: Could not get DeveloperModeStatus: %s\nPlease note that this feature is only available on iOS 16+.\n", lockdownd_strerror(lerr));
121 lockdownd_client_free(lockdown);
122 idevice_free(device);
123 return -2;
124 }
125
126 uint8_t dev_mode_status = 0;
127 plist_get_bool_val(val, &dev_mode_status);
128 plist_free(val);
129
130 lockdownd_client_free(lockdown);
131 idevice_free(device);
132
133 return dev_mode_status;
134}
135
136static int amfi_service_send_msg(property_list_service_client_t amfi, plist_t msg)
137{
138 int res;
139 property_list_service_error_t perr;
140
141 perr = property_list_service_send_xml_plist(amfi, plist_copy(msg));
142 if (perr != PROPERTY_LIST_SERVICE_E_SUCCESS) {
143 fprintf(stderr, "Could not send request to device: %d\n", perr);
144 res = 2;
145 } else {
146 plist_t reply = NULL;
147 perr = property_list_service_receive_plist(amfi, &reply);
148 if (perr == PROPERTY_LIST_SERVICE_E_SUCCESS) {
149 plist_t val = plist_dict_get_item(reply, "Error");
150 if (val) {
151 const char* err = plist_get_string_ptr(val, NULL);
152 fprintf(stderr, "Request failed: %s\n", err);
153 if (strstr(err, "passcode")) {
154 res = 2;
155 } else {
156 res = 1;
157 }
158 } else {
159 res = plist_dict_get_item(reply, "success") ? 0 : 1;
160 }
161 } else {
162 fprintf(stderr, "Could not receive reply from device: %d\n", perr);
163 res = 2;
164 }
165 plist_free(reply);
166 }
167 return res;
168}
169
170static int amfi_send_action(idevice_t device, unsigned int action)
171{
172 lockdownd_client_t lockdown = NULL;
173 lockdownd_service_descriptor_t service = NULL;
174 lockdownd_error_t lerr;
175
176 if (LOCKDOWN_E_SUCCESS != (lerr = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) {
177 fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", lerr);
178 return 1;
179 }
180
181 lerr = lockdownd_start_service(lockdown, AMFI_LOCKDOWN_SERVICE_NAME, &service);
182 if (lerr != LOCKDOWN_E_SUCCESS) {
183 fprintf(stderr, "Could not start service %s: %s\nPlease note that this feature is only available on iOS 16+.\n", AMFI_LOCKDOWN_SERVICE_NAME, lockdownd_strerror(lerr));
184 lockdownd_client_free(lockdown);
185 return 1;
186 }
187 lockdownd_client_free(lockdown);
188 lockdown = NULL;
189
190 property_list_service_client_t amfi = NULL;
191 if (property_list_service_client_new(device, service, &amfi) != PROPERTY_LIST_SERVICE_E_SUCCESS) {
192 fprintf(stderr, "Could not connect to %s on device\n", AMFI_LOCKDOWN_SERVICE_NAME);
193 if (service)
194 lockdownd_service_descriptor_free(service);
195 idevice_free(device);
196 return 1;
197 }
198 lockdownd_service_descriptor_free(service);
199
200 plist_t dict = plist_new_dict();
201 plist_dict_set_item(dict, "action", plist_new_uint(action));
202
203 int result = amfi_service_send_msg(amfi, dict);
204 plist_free(dict);
205
206 property_list_service_client_free(amfi);
207 amfi = NULL;
208
209 return result;
210}
211
212static int device_connected = 0;
213
214static void device_event_cb(const idevice_event_t* event, void* userdata)
215{
216 if (use_network && event->conn_type != CONNECTION_NETWORK) {
217 return;
218 }
219 if (!use_network && event->conn_type != CONNECTION_USBMUXD) {
220 return;
221 }
222 if (event->event == IDEVICE_DEVICE_ADD) {
223 if (!udid) {
224 udid = strdup(event->udid);
225 }
226 if (strcmp(udid, event->udid) == 0) {
227 device_connected = 1;
228 }
229 } else if (event->event == IDEVICE_DEVICE_REMOVE) {
230 if (strcmp(udid, event->udid) == 0) {
231 device_connected = 0;
232 }
233 }
234}
235
236
237#define WAIT_INTERVAL 200000
238#define WAIT_MAX(x) (x * (1000000 / WAIT_INTERVAL))
239#define WAIT_FOR(cond, timeout) { int __repeat = WAIT_MAX(timeout); while (!(cond) && __repeat-- > 0) { __usleep(WAIT_INTERVAL); } }
240
241int main(int argc, char *argv[])
242{
243 idevice_t device = NULL;
244 idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR;
245 lockdownd_client_t lockdown = NULL;
246 lockdownd_error_t lerr = LOCKDOWN_E_UNKNOWN_ERROR;
247 int res = 0;
248 int i;
249 int op = -1;
250 plist_t val = NULL;
251
252 int c = 0;
253 const struct option longopts[] = {
254 { "debug", no_argument, NULL, 'd' },
255 { "help", no_argument, NULL, 'h' },
256 { "udid", required_argument, NULL, 'u' },
257 { "network", no_argument, NULL, 'n' },
258 { "version", no_argument, NULL, 'v' },
259 { NULL, 0, NULL, 0}
260 };
261
262#ifndef WIN32
263 signal(SIGPIPE, SIG_IGN);
264#endif
265 /* parse cmdline args */
266 while ((c = getopt_long(argc, argv, "dhu:nv", longopts, NULL)) != -1) {
267 switch (c) {
268 case 'd':
269 idevice_set_debug_level(1);
270 break;
271 case 'u':
272 if (!*optarg) {
273 fprintf(stderr, "ERROR: UDID argument must not be empty!\n");
274 print_usage(argc, argv, 1);
275 return 2;
276 }
277 udid = optarg;
278 break;
279 case 'n':
280 use_network = 1;
281 break;
282 case 'h':
283 print_usage(argc, argv, 0);
284 return 0;
285 case 'v':
286 printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
287 return 0;
288 default:
289 print_usage(argc, argv, 1);
290 return 2;
291 }
292 }
293 argc -= optind;
294 argv += optind;
295
296 if (!argv[0]) {
297 fprintf(stderr, "ERROR: Missing command.\n");
298 print_usage(argc+optind, argv-optind, 1);
299 return 2;
300 }
301
302 i = 0;
303 if (!strcmp(argv[i], "list")) {
304 op = OP_LIST;
305 }
306 else if (!strcmp(argv[i], "enable")) {
307 op = OP_ENABLE;
308 }
309 else if (!strcmp(argv[i], "arm")) {
310 op = OP_ARM;
311 }
312 else if (!strcmp(argv[i], "confirm")) {
313 op = OP_CONFIRM;
314 }
315 else if (!strcmp(argv[i], "reveal")) {
316 op = OP_REVEAL;
317 }
318
319 if ((op == -1) || (op >= NUM_OPS)) {
320 fprintf(stderr, "ERROR: Unsupported command '%s'\n", argv[i]);
321 print_usage(argc+optind, argv-optind, 1);
322 return 2;
323 }
324
325 if (op == OP_LIST) {
326 idevice_info_t *dev_list = NULL;
327
328 if (idevice_get_device_list_extended(&dev_list, &i) < 0) {
329 fprintf(stderr, "ERROR: Unable to retrieve device list!\n");
330 return -1;
331 }
332 if (i > 0) {
333 printf("%-40s %s\n", "Device", "DeveloperMode");
334 }
335 for (i = 0; dev_list[i] != NULL; i++) {
336 if (dev_list[i]->conn_type == CONNECTION_USBMUXD && use_network) continue;
337 if (dev_list[i]->conn_type == CONNECTION_NETWORK && !use_network) continue;
338 if (udid && (strcmp(dev_list[i]->udid, udid) != 0)) continue;
339 int mode = get_developer_mode_status(dev_list[i]->udid, use_network);
340 const char *mode_str = "N/A";
341 if (mode == 1) {
342 mode_str = "enabled";
343 } else if (mode == 0) {
344 mode_str = "disabled";
345 }
346 printf("%-40s %s\n", dev_list[i]->udid, mode_str);
347 }
348 idevice_device_list_extended_free(dev_list);
349
350 return 0;
351 }
352
353 idevice_subscription_context_t context = NULL;
354 idevice_events_subscribe(&context, device_event_cb, NULL);
355
356 WAIT_FOR(device_connected, 10);
357
358 ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX);
359 if (ret != IDEVICE_E_SUCCESS) {
360 if (udid) {
361 printf("No device found with udid %s.\n", udid);
362 } else {
363 printf("No device found.\n");
364 }
365 return 1;
366 }
367
368 if (!udid) {
369 idevice_get_udid(device, &udid);
370 }
371
372 if (LOCKDOWN_E_SUCCESS != (lerr = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) {
373 fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", lerr);
374 idevice_free(device);
375 return 1;
376 }
377
378 lerr = lockdownd_get_value(lockdown, "com.apple.security.mac.amfi", "DeveloperModeStatus", &val);
379 lockdownd_client_free(lockdown);
380 lockdown = NULL;
381 if (lerr != LOCKDOWN_E_SUCCESS) {
382 fprintf(stderr, "ERROR: Could not get DeveloperModeStatus: %s\nPlease note that this feature is only available on iOS 16+.\n", lockdownd_strerror(lerr));
383 idevice_free(device);
384 return 1;
385 }
386
387 uint8_t dev_mode_status = 0;
388 plist_get_bool_val(val, &dev_mode_status);
389
390 if ((op == OP_ENABLE || op == OP_ARM) && dev_mode_status) {
391 if (dev_mode_status) {
392 idevice_free(device);
393 printf("DeveloperMode is already enabled.\n");
394 return 0;
395 }
396 res = 0;
397 } else {
398 if (op == OP_ENABLE || op == OP_ARM) {
399 res = amfi_send_action(device, DEV_MODE_ARM);
400 if (res == 0) {
401 if (op == OP_ARM) {
402 printf("%s: Developer Mode armed, device will reboot now.\n", udid);
403 } else {
404 printf("%s: Developer Mode armed, waiting for reboot...\n", udid);
405
406 do {
407 // waiting for device to disconnect...
408 idevice_free(device);
409 device = NULL;
410 WAIT_FOR(!device_connected, 40);
411 if (device_connected) {
412 printf("%s: ERROR: Device didn't reboot?!\n", udid);
413 res = 2;
414 break;
415 }
416 printf("disconnected\n");
417
418 // waiting for device to reconnect...
419 WAIT_FOR(device_connected, 60);
420 if (!device_connected) {
421 printf("%s: ERROR: Device didn't re-connect?!\n", udid);
422 res = 2;
423 break;
424 }
425 printf("connected\n");
426
427 idevice_new(&device, udid);
428 res = amfi_send_action(device, DEV_MODE_ENABLE);
429 } while (0);
430 if (res == 0) {
431 printf("%s: Developer Mode successfully enabled.\n", udid);
432 } else {
433 printf("%s: Failed to enable developer mode (%d)\n", udid, res);
434 }
435 }
436 } else if (res == 2) {
437 amfi_send_action(device, DEV_MODE_REVEAL);
438 printf("%s: Developer Mode could not be enabled because the device has a passcode set. You have to enable it on the device itself under Settings -> Privacy & Security -> Developer Mode.\n", udid);
439 } else {
440 printf("%s: Failed to arm Developer Mode (%d)\n", udid, res);
441 }
442 } else if (op == OP_CONFIRM) {
443 res = amfi_send_action(device, DEV_MODE_ENABLE);
444 if (res == 0) {
445 printf("%s: Developer Mode successfully enabled.\n", udid);
446 } else {
447 printf("%s: Failed to enable Developer Mode (%d)\n", udid, res);
448 }
449 } else if (op == OP_REVEAL) {
450 res = amfi_send_action(device, DEV_MODE_REVEAL);
451 if (res == 0) {
452 printf("%s: Developer Mode menu revealed successfully.\n", udid);
453 } else {
454 printf("%s: Failed to reveal Developer Mode menu (%d)\n", udid, res);
455 }
456 }
457 }
458
459 idevice_free(device);
460
461 return res;
462}