/* * ideviceactivation.c * A command line tool to handle the activation process * * Copyright (c) 2016-2019 Nikias Bassen, All Rights Reserved. * Copyright (c) 2014-2015 Martin Szulecki, All Rights Reserved. * Copyright (c) 2011-2015 Mirell Development, All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #ifndef WIN32 #include #endif #include #include #include #include #include #ifdef WIN32 #include #include #else #include #endif static void print_usage(int argc, char **argv) { char *name = NULL; name = strrchr(argv[0], '/'); printf("Usage: %s COMMAND [OPTIONS]\n", (name ? name + 1: argv[0])); printf("\n"); printf("Activate or deactivate a device.\n"); printf("\n"); printf("Where COMMAND is one of:\n"); printf(" activate\t\tattempt to activate the device\n"); printf(" deactivate\t\tdeactivate the device\n"); printf(" state\t\t\tquery device about its activation state\n"); printf("\n"); printf("The following OPTIONS are accepted:\n"); printf(" -d, --debug\t\tenable communication debugging\n"); printf(" -u, --udid UDID\ttarget specific device by UDID\n"); printf(" -n, --network\t\tconnect to network device\n"); printf(" -b, --batch\t\texplicitly run in non-interactive mode (default: auto-detect)\n"); printf(" -s, --service URL\tuse activation webservice at URL instead of default\n"); printf(" -v, --version\t\tprint version information and exit\n"); printf(" -h, --help\t\tprints usage information\n"); printf("\n"); printf("Homepage: <" PACKAGE_URL ">\n"); printf("Bug Reports: <" PACKAGE_BUGREPORT ">\n"); } #ifdef WIN32 #define BS_CC '\b' #define my_getch getch #else #define BS_CC 0x7f static int my_getch(void) { struct termios oldt, newt; int ch; tcgetattr(STDIN_FILENO, &oldt); newt = oldt; newt.c_lflag &= ~(ICANON | ECHO); tcsetattr(STDIN_FILENO, TCSANOW, &newt); ch = getchar(); tcsetattr(STDIN_FILENO, TCSANOW, &oldt); return ch; } #endif static void get_user_input(char *buf, int maxlen, int secure) { int len = 0; int c; while ((c = my_getch())) { if ((c == '\r') || (c == '\n')) { break; } else if (isprint(c)) { if (len < maxlen-1) buf[len++] = c; fputc((secure) ? '*' : c, stdout); } else if (c == BS_CC) { if (len > 0) { fputs("\b \b", stdout); len--; } } } fputs("\n", stdout); buf[len] = 0; } int main(int argc, char *argv[]) { idevice_t device = NULL; idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR; lockdownd_client_t lockdown = NULL; mobileactivation_client_t ma = NULL; idevice_activation_request_t request = NULL; idevice_activation_response_t response = NULL; const char* response_title = NULL; const char* response_description = NULL; char* field_key = NULL; char* field_label = NULL; char input[1024]; plist_t fields = NULL; plist_dict_iter iter = NULL; plist_t record = NULL; char *udid = NULL; char *signing_service_url = NULL; int use_mobileactivation = 0; int session_mode = 0; int i; int interactive = 1; int result = EXIT_FAILURE; int use_network = 0; typedef enum { OP_NONE = 0, OP_ACTIVATE, OP_DEACTIVATE, OP_GETSTATE } op_t; op_t op = OP_NONE; #ifndef WIN32 signal(SIGPIPE, SIG_IGN); #endif /* parse cmdline args */ for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) { idevice_set_debug_level(1); idevice_activation_set_debug_level(1); continue; } else if (!strcmp(argv[i], "-u") || !strcmp(argv[i], "--udid")) { i++; if (!argv[i] || !*argv[i]) { print_usage(argc, argv); return EXIT_FAILURE; } udid = argv[i]; continue; } else if (!strcmp(argv[i], "-n") || !strcmp(argv[i], "--network")) { use_network = 1; continue; } else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--service")) { i++; if (!argv[i]) { print_usage(argc, argv); return EXIT_FAILURE; } signing_service_url = argv[i]; continue; } else if (!strcmp(argv[i], "-b") || !strcmp(argv[i], "--batch")) { interactive = 0; continue; } else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { print_usage(argc, argv); return EXIT_SUCCESS; } else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--version")) { printf("ideviceactivation %s\n", PACKAGE_VERSION); return EXIT_SUCCESS; } else if (!strcmp(argv[i], "activate")) { op = OP_ACTIVATE; continue; } else if (!strcmp(argv[i], "deactivate")) { op = OP_DEACTIVATE; continue; } else if (!strcmp(argv[i], "state")) { op = OP_GETSTATE; continue; } else { print_usage(argc, argv); return EXIT_SUCCESS; } } if (interactive) { if (!isatty(fileno(stdin)) || !isatty(fileno(stdout))) { interactive = 0; } } if (op == OP_NONE) { print_usage(argc, argv); return EXIT_FAILURE; } ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX); if (ret != IDEVICE_E_SUCCESS) { if (udid) { printf("ERROR: Device %s not found!\n", udid); } else { printf("ERROR: No device found!\n"); } return EXIT_FAILURE; } if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(device, &lockdown, "ideviceactivation")) { fprintf(stderr, "Failed to connect to lockdownd\n"); result = EXIT_FAILURE; goto cleanup; } plist_t p_version = NULL; uint32_t product_version = 0; if (lockdownd_get_value(lockdown, NULL, "ProductVersion", &p_version) == LOCKDOWN_E_SUCCESS) { int vers[3] = {0, 0, 0}; char *s_version = NULL; plist_get_string_val(p_version, &s_version); if (s_version && sscanf(s_version, "%d.%d.%d", &vers[0], &vers[1], &vers[2]) >= 2) { product_version = ((vers[0] & 0xFF) << 16) | ((vers[1] & 0xFF) << 8) | (vers[2] & 0xFF); } free(s_version); } plist_free(p_version); if (op == OP_ACTIVATE && product_version >= 0x0A0200) { /* The activation server will not acknowledge the activation for iOS >= 10.2 anymore. Let's warn the user about this. */ plist_t state = NULL; lockdownd_get_value(lockdown, NULL, "ActivationState", &state); if (state) { char *state_str = NULL; plist_get_string_val(state, &state_str); if (state_str && strcmp(state_str, "Unactivated") != 0) { printf("NOTE: This device appears to be already activated. The server might report an error 'Device Unknown' instead of acknowledging the activation.\n"); } free(state_str); plist_free(state); } } // check if we should use the new mobileactivation service lockdownd_service_descriptor_t svc = NULL; if (lockdownd_start_service(lockdown, MOBILEACTIVATION_SERVICE_NAME, &svc) == LOCKDOWN_E_SUCCESS) { mobileactivation_error_t maerr = mobileactivation_client_new(device, svc, &ma); lockdownd_service_descriptor_free(svc); svc = NULL; if (maerr != MOBILEACTIVATION_E_SUCCESS) { fprintf(stderr, "Failed to connect to %s\n", MOBILEACTIVATION_SERVICE_NAME); result = EXIT_FAILURE; goto cleanup; } use_mobileactivation = 1; } switch (op) { case OP_DEACTIVATE: if (use_mobileactivation) { // deactivate device using mobileactivation if (MOBILEACTIVATION_E_SUCCESS != mobileactivation_deactivate(ma)) { fprintf(stderr, "Failed to deactivate device.\n"); result = EXIT_FAILURE; goto cleanup; } mobileactivation_client_free(ma); ma = NULL; } else { // deactivate device using lockdown if (LOCKDOWN_E_SUCCESS != lockdownd_deactivate(lockdown)) { fprintf(stderr, "Failed to deactivate device.\n"); result = EXIT_FAILURE; goto cleanup; } } result = EXIT_SUCCESS; printf("Successfully deactivated device.\n"); break; case OP_ACTIVATE: default: if (use_mobileactivation) { // create activation request from mobileactivation plist_t ainfo = NULL; if ((product_version >= 0x0A0000) || (mobileactivation_create_activation_info(ma, &ainfo) != MOBILEACTIVATION_E_SUCCESS)) { session_mode = 1; } mobileactivation_client_free(ma); ma = NULL; if (session_mode) { /* first grab session blob from device required for drmHandshake */ plist_t blob = NULL; if (mobileactivation_client_start_service(device, &ma, "ideviceactivation") != MOBILEACTIVATION_E_SUCCESS) { fprintf(stderr, "Failed to connect to %s\n", MOBILEACTIVATION_SERVICE_NAME); result = EXIT_FAILURE; goto cleanup; } if (mobileactivation_create_activation_session_info(ma, &blob) != MOBILEACTIVATION_E_SUCCESS) { fprintf(stderr, "Failed to get ActivationSessionInfo from mobileactivation\n"); result = EXIT_FAILURE; goto cleanup; } mobileactivation_client_free(ma); ma = NULL; /* create drmHandshake request with blob from device */ if (idevice_activation_drm_handshake_request_new(IDEVICE_ACTIVATION_CLIENT_MOBILE_ACTIVATION, &request) != IDEVICE_ACTIVATION_E_SUCCESS) { fprintf(stderr, "Failed to create drmHandshake request.\n"); result = EXIT_FAILURE; goto cleanup; } idevice_activation_request_set_fields(request, blob); plist_free(blob); if (signing_service_url) { idevice_activation_request_set_url(request, signing_service_url); } /* send request to server and get response */ if (idevice_activation_send_request(request, &response) != IDEVICE_ACTIVATION_E_SUCCESS) { fprintf(stderr, "Failed to get drmHandshake result from activation server.\n"); result = EXIT_FAILURE; goto cleanup; } plist_t handshake_response = NULL; idevice_activation_response_get_fields(response, &handshake_response); idevice_activation_response_free(response); response = NULL; /* use handshake response to get activation info from device */ if (mobileactivation_client_start_service(device, &ma, "ideviceactivation") != MOBILEACTIVATION_E_SUCCESS) { fprintf(stderr, "Failed to connect to %s\n", MOBILEACTIVATION_SERVICE_NAME); result = EXIT_FAILURE; goto cleanup; } if ((mobileactivation_create_activation_info_with_session(ma, handshake_response, &ainfo) != MOBILEACTIVATION_E_SUCCESS) || !ainfo || (plist_get_node_type(ainfo) != PLIST_DICT)) { fprintf(stderr, "Failed to get ActivationInfo from mobileactivation\n"); result = EXIT_FAILURE; goto cleanup; } mobileactivation_client_free(ma); ma = NULL; } else if (!ainfo || plist_get_node_type(ainfo) != PLIST_DICT) { fprintf(stderr, "Failed to get ActivationInfo from mobileactivation\n"); result = EXIT_FAILURE; goto cleanup; } /* create activation request */ if (idevice_activation_request_new(IDEVICE_ACTIVATION_CLIENT_MOBILE_ACTIVATION, &request) != IDEVICE_ACTIVATION_E_SUCCESS) { fprintf(stderr, "Failed to create activation request.\n"); result = EXIT_FAILURE; goto cleanup; } /* add activation info to request */ plist_t request_fields = plist_new_dict(); plist_dict_set_item(request_fields, "activation-info", ainfo); idevice_activation_request_set_fields(request, request_fields); } else { // create activation request from lockdown if (idevice_activation_request_new_from_lockdownd( IDEVICE_ACTIVATION_CLIENT_MOBILE_ACTIVATION, lockdown, &request) != IDEVICE_ACTIVATION_E_SUCCESS) { fprintf(stderr, "Failed to create activation request.\n"); result = EXIT_FAILURE; goto cleanup; } } lockdownd_client_free(lockdown); lockdown = NULL; if (request && signing_service_url) { idevice_activation_request_set_url(request, signing_service_url); } while(1) { if (idevice_activation_send_request(request, &response) != IDEVICE_ACTIVATION_E_SUCCESS) { fprintf(stderr, "Failed to send request or retrieve response.\n"); // Here response might have some content that could't be correctly interpreted (parsed) // by the library. Printing out the content could help to identify the cause of the error. result = EXIT_FAILURE; goto cleanup; } if (idevice_activation_response_has_errors(response)) { fprintf(stderr, "Activation server reports errors.\n"); idevice_activation_response_get_title(response, &response_title); if (response_title) { fprintf(stderr, "\t%s\n", response_title); } idevice_activation_response_get_description(response, &response_description); if (response_description) { fprintf(stderr, "\t%s\n", response_description); } result = EXIT_FAILURE; goto cleanup; } idevice_activation_response_get_activation_record(response, &record); if (record) { if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(device, &lockdown, "ideviceactivation")) { fprintf(stderr, "Failed to connect to lockdownd\n"); result = EXIT_FAILURE; goto cleanup; } if (use_mobileactivation) { svc = NULL; if (lockdownd_start_service(lockdown, MOBILEACTIVATION_SERVICE_NAME, &svc) != LOCKDOWN_E_SUCCESS) { fprintf(stderr, "Failed to start service %s\n", MOBILEACTIVATION_SERVICE_NAME); result = EXIT_FAILURE; goto cleanup; } mobileactivation_error_t maerr = mobileactivation_client_new(device, svc, &ma); lockdownd_service_descriptor_free(svc); svc = NULL; if (maerr != MOBILEACTIVATION_E_SUCCESS) { fprintf(stderr, "Failed to connect to %s\n", MOBILEACTIVATION_SERVICE_NAME); result = EXIT_FAILURE; goto cleanup; } if (session_mode) { plist_t headers = NULL; idevice_activation_response_get_headers(response, &headers); if (MOBILEACTIVATION_E_SUCCESS != mobileactivation_activate_with_session(ma, record, headers)) { plist_free(headers); fprintf(stderr, "Failed to activate device with record.\n"); result = EXIT_FAILURE; goto cleanup; } plist_free(headers); } else { if (MOBILEACTIVATION_E_SUCCESS != mobileactivation_activate(ma, record)) { fprintf(stderr, "Failed to activate device with record.\n"); result = EXIT_FAILURE; goto cleanup; } } } else { // activate device using lockdown if (LOCKDOWN_E_SUCCESS != lockdownd_activate(lockdown, record)) { plist_t state = NULL; lockdownd_get_value(lockdown, NULL, "ActivationState", &state); int success = 0; if (state && plist_get_node_type(state) == PLIST_STRING) { char *strval = NULL; plist_get_string_val(state, &strval); if (strval && strcmp(strval, "Unactivated")) { success = 1; } free(strval); } if (!success) { fprintf(stderr, "Failed to activate device with record.\n"); result = EXIT_FAILURE; goto cleanup; } } } // set ActivationStateAcknowledged if we succeeded if (LOCKDOWN_E_SUCCESS != lockdownd_set_value(lockdown, NULL, "ActivationStateAcknowledged", plist_new_bool(1))) { fprintf(stderr, "Failed to set ActivationStateAcknowledged on device.\n"); result = EXIT_FAILURE; goto cleanup; } break; } else { if (idevice_activation_response_is_activation_acknowledged(response)) { printf("Activation server reports that device is already activated.\n"); result = EXIT_SUCCESS; goto cleanup; } idevice_activation_response_get_title(response, &response_title); if (response_title) { fprintf(stderr, "Server reports:\n%s\n", response_title); } idevice_activation_response_get_description(response, &response_description); if (response_description) { fprintf(stderr, "Server reports:\n%s\n", response_description); } idevice_activation_response_get_fields(response, &fields); if (!fields || plist_dict_get_size(fields) == 0) { // we have no activation record, no reported erros, no acknowledgment and no fields to send fprintf(stderr, "Unknown error.\n"); result = EXIT_FAILURE; goto cleanup; } plist_dict_new_iter(fields, &iter); if (!iter) { fprintf(stderr, "Unknown error.\n"); result = EXIT_FAILURE; goto cleanup; } idevice_activation_request_free(request); request = NULL; if (idevice_activation_request_new( IDEVICE_ACTIVATION_CLIENT_MOBILE_ACTIVATION, &request) != IDEVICE_ACTIVATION_E_SUCCESS) { fprintf(stderr, "Could not create new request.\n"); result = EXIT_FAILURE; goto cleanup; } idevice_activation_request_set_fields_from_response(request, response); int interactive_count = 0; do { field_key = NULL; plist_dict_next_item(fields, iter, &field_key, NULL); if (field_key) { if (idevice_activation_response_field_requires_input(response, field_key)) { idevice_activation_response_get_label(response, field_key, &field_label); if (interactive) { char *field_placeholder = NULL; int secure = idevice_activation_response_field_secure_input(response, field_key); idevice_activation_response_get_placeholder(response, field_key, &field_placeholder); printf("input %s", field_label ? field_label : field_key); if (field_placeholder) { printf(" (%s)", field_placeholder); free(field_placeholder); } printf(": "); fflush(stdout); fflush(stdin); get_user_input(input, 1023, secure); } else { fprintf(stderr, "Server requires input for '%s' but we're not running interactively.\n", field_label ? field_label : field_key); strcpy(input, ""); interactive_count++; } idevice_activation_request_set_field(request, field_key, input); if (field_label) { free(field_label); field_label = NULL; } } } } while(field_key); free(iter); iter = NULL; idevice_activation_response_free(response); response = NULL; if (interactive_count > 0 && !interactive) { fprintf(stderr, "Failed to activate device.\n"); result = EXIT_FAILURE; goto cleanup; } } } result = EXIT_SUCCESS; printf("Successfully activated device.\n"); break; case OP_GETSTATE: { plist_t state = NULL; if (use_mobileactivation) { mobileactivation_get_activation_state(ma, &state); } else { lockdownd_get_value(lockdown, NULL, "ActivationState", &state); } if (plist_get_node_type(state) == PLIST_STRING) { char *s_state = NULL; plist_get_string_val(state, &s_state); printf("ActivationState: %s\n", s_state); free(s_state); } else { printf("Error getting activation state.\n"); } } break; } cleanup: if (request) idevice_activation_request_free(request); if (response) idevice_activation_response_free(response); if (fields) plist_free(fields); if (field_label) free(field_label); if (iter) free(iter); if (record) plist_free(record); if (ma) mobileactivation_client_free(ma); if (lockdown) lockdownd_client_free(lockdown); if (device) idevice_free(device); return result; }