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