blob: 49f757c750ffb7c95d59a61b1a0427f298c6269b [file] [log] [blame]
/*
* lockdown.c
* com.apple.mobile.lockdownd service implementation.
*
* Copyright (c) 2009-2015 Martin Szulecki All Rights Reserved.
* Copyright (c) 2014-2015 Nikias Bassen All Rights Reserved.
* Copyright (c) 2010 Bryan Forbes All Rights Reserved.
* Copyright (c) 2008 Zach C. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <stdlib.h>
#define _GNU_SOURCE 1
#define __USE_GNU 1
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <plist/plist.h>
#include "property_list_service.h"
#include "lockdown.h"
#include "idevice.h"
#include "common/debug.h"
#include "common/userpref.h"
#include "common/utils.h"
#include "asprintf.h"
#ifdef WIN32
#include <windows.h>
#define sleep(x) Sleep(x*1000)
#endif
struct st_lockdownd_error_str_map {
const char *lockdown_errstr;
const char *errstr;
lockdownd_error_t errcode;
};
static struct st_lockdownd_error_str_map lockdownd_error_str_map[] = {
{ "InvalidResponse", "Invalid response", LOCKDOWN_E_INVALID_RESPONSE },
{ "MissingKey", "Missing key", LOCKDOWN_E_MISSING_KEY },
{ "MissingValue", "Missing value", LOCKDOWN_E_MISSING_VALUE },
{ "GetProhibited", "Get value prohibited", LOCKDOWN_E_GET_PROHIBITED },
{ "SetProhibited", "Set value prohibited", LOCKDOWN_E_SET_PROHIBITED },
{ "RemoveProhibited", "Remove value prohibited", LOCKDOWN_E_REMOVE_PROHIBITED },
{ "ImmutableValue", "Immutable value", LOCKDOWN_E_IMMUTABLE_VALUE },
{ "PasswordProtected", "Password protected", LOCKDOWN_E_PASSWORD_PROTECTED },
{ "UserDeniedPairing", "User denied pairing", LOCKDOWN_E_USER_DENIED_PAIRING },
{ "PairingDialogResponsePending", "Pairing dialog response pending", LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING },
{ "MissingHostID", "Missing HostID", LOCKDOWN_E_MISSING_HOST_ID },
{ "InvalidHostID", "Invalid HostID", LOCKDOWN_E_INVALID_HOST_ID },
{ "SessionActive", "Session active", LOCKDOWN_E_SESSION_ACTIVE },
{ "SessionInactive", "Session inactive", LOCKDOWN_E_SESSION_INACTIVE },
{ "MissingSessionID", "Missing session ID", LOCKDOWN_E_MISSING_SESSION_ID },
{ "InvalidSessionID", "Invalid session ID", LOCKDOWN_E_INVALID_SESSION_ID },
{ "MissingService", "Missing service", LOCKDOWN_E_MISSING_SERVICE },
{ "InvalidService", "Invalid service", LOCKDOWN_E_INVALID_SERVICE },
{ "ServiceLimit", "Service limit reached", LOCKDOWN_E_SERVICE_LIMIT },
{ "MissingPairRecord", "Missing pair record", LOCKDOWN_E_MISSING_PAIR_RECORD },
{ "SavePairRecordFailed", "Saving pair record failed", LOCKDOWN_E_SAVE_PAIR_RECORD_FAILED },
{ "InvalidPairRecord", "Invalid pair record", LOCKDOWN_E_INVALID_PAIR_RECORD },
{ "InvalidActivationRecord", "Invalid activation record", LOCKDOWN_E_INVALID_ACTIVATION_RECORD },
{ "MissingActivationRecord", "Missing activation record", LOCKDOWN_E_MISSING_ACTIVATION_RECORD },
{ "ServiceProhibited", "Service prohibited", LOCKDOWN_E_SERVICE_PROHIBITED },
{ "EscrowLocked", "Escrow lockded", LOCKDOWN_E_ESCROW_LOCKED },
{ "PairingProhibitedOverThisConnection", "Pairing prohibited over this connection", LOCKDOWN_E_PAIRING_PROHIBITED_OVER_THIS_CONNECTION },
{ "FMiPProtected", "Find My iPhone/iPod/iPad protected", LOCKDOWN_E_FMIP_PROTECTED },
{ "MCProtected", "MC protected" , LOCKDOWN_E_MC_PROTECTED },
{ "MCChallengeRequired", "MC challenge required", LOCKDOWN_E_MC_CHALLENGE_REQUIRED },
{ NULL, NULL, 0 }
};
/**
* Convert an error string identifier to a lockdownd_error_t value.
* Used internally to get correct error codes from a response.
*
* @param name The error name to convert.
*
* @return A matching lockdownd_error_t error code,
* LOCKDOWN_E_UNKNOWN_ERROR otherwise.
*/
static lockdownd_error_t lockdownd_strtoerr(const char* name)
{
lockdownd_error_t err = LOCKDOWN_E_UNKNOWN_ERROR;
int i = 0;
while (lockdownd_error_str_map[i].lockdown_errstr) {
if (strcmp(lockdownd_error_str_map[i].lockdown_errstr, name) == 0) {
return lockdownd_error_str_map[i].errcode;
}
i++;
}
return err;
}
/**
* Convert a property_list_service_error_t value to a lockdownd_error_t
* value. Used internally to get correct error codes.
*
* @param err A property_list_service_error_t error code
*
* @return A matching lockdownd_error_t error code,
* LOCKDOWND_E_UNKNOWN_ERROR otherwise.
*/
static lockdownd_error_t lockdownd_error(property_list_service_error_t err)
{
switch (err) {
case PROPERTY_LIST_SERVICE_E_SUCCESS:
return LOCKDOWN_E_SUCCESS;
case PROPERTY_LIST_SERVICE_E_INVALID_ARG:
return LOCKDOWN_E_INVALID_ARG;
case PROPERTY_LIST_SERVICE_E_PLIST_ERROR:
return LOCKDOWN_E_PLIST_ERROR;
case PROPERTY_LIST_SERVICE_E_MUX_ERROR:
return LOCKDOWN_E_MUX_ERROR;
case PROPERTY_LIST_SERVICE_E_SSL_ERROR:
return LOCKDOWN_E_SSL_ERROR;
case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT:
return LOCKDOWN_E_RECEIVE_TIMEOUT;
default:
break;
}
return LOCKDOWN_E_UNKNOWN_ERROR;
}
/**
* Internally used function for checking the result from lockdown's answer
* plist to a previously sent request.
*
* @param dict The plist to evaluate.
* @param query_match Name of the request to match or NULL if no match is
* required.
*
* @return LOCKDOWN_E_SUCCESS when the result is 'Success',
* LOCKDOWN_E_UNKNOWN_ERROR when the result is 'Failure',
* or a specific error code if derieved from the result.
*/
static lockdownd_error_t lockdown_check_result(plist_t dict, const char *query_match)
{
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t query_node = plist_dict_get_item(dict, "Request");
if (!query_node) {
return ret;
}
if (plist_get_node_type(query_node) != PLIST_STRING) {
return ret;
} else {
char *query_value = NULL;
plist_get_string_val(query_node, &query_value);
if (!query_value) {
return ret;
}
if (query_match && (strcmp(query_value, query_match) != 0)) {
free(query_value);
return ret;
}
free(query_value);
}
plist_t result_node = plist_dict_get_item(dict, "Result");
if (!result_node) {
/* iOS 5: the 'Result' key is not present anymore.
But we need to check for the 'Error' key. */
plist_t err_node = plist_dict_get_item(dict, "Error");
if (err_node) {
if (plist_get_node_type(err_node) == PLIST_STRING) {
char *err_value = NULL;
plist_get_string_val(err_node, &err_value);
if (err_value) {
debug_info("ERROR: %s", err_value);
ret = lockdownd_strtoerr(err_value);
free(err_value);
} else {
debug_info("ERROR: unknown error occurred");
}
}
return ret;
}
ret = LOCKDOWN_E_SUCCESS;
return ret;
}
plist_type result_type = plist_get_node_type(result_node);
if (result_type == PLIST_STRING) {
char *result_value = NULL;
plist_get_string_val(result_node, &result_value);
if (result_value) {
if (!strcmp(result_value, "Success")) {
ret = LOCKDOWN_E_SUCCESS;
} else if (!strcmp(result_value, "Failure")) {
ret = LOCKDOWN_E_UNKNOWN_ERROR;
} else {
debug_info("ERROR: unknown result value '%s'", result_value);
}
}
if (result_value)
free(result_value);
}
return ret;
}
/**
* Adds a label key with the passed value to a plist dict node.
*
* @param plist The plist to add the key to
* @param label The value for the label key
*
*/
static void plist_dict_add_label(plist_t plist, const char *label)
{
if (plist && label) {
if (plist_get_node_type(plist) == PLIST_DICT)
plist_dict_set_item(plist, "Label", plist_new_string(label));
}
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_stop_session(lockdownd_client_t client, const char *session_id)
{
if (!client)
return LOCKDOWN_E_INVALID_ARG;
if (!session_id) {
debug_info("no session_id given, cannot stop session");
return LOCKDOWN_E_INVALID_ARG;
}
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t dict = plist_new_dict();
plist_dict_add_label(dict, client->label);
plist_dict_set_item(dict,"Request", plist_new_string("StopSession"));
plist_dict_set_item(dict,"SessionID", plist_new_string(session_id));
debug_info("stopping session %s", session_id);
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
ret = lockdownd_receive(client, &dict);
if (!dict) {
debug_info("LOCKDOWN_E_PLIST_ERROR");
return LOCKDOWN_E_PLIST_ERROR;
}
ret = lockdown_check_result(dict, "StopSession");
if (ret == LOCKDOWN_E_SUCCESS) {
debug_info("success");
}
plist_free(dict);
dict = NULL;
if (client->session_id) {
free(client->session_id);
client->session_id = NULL;
}
if (client->ssl_enabled) {
property_list_service_disable_ssl(client->parent);
client->ssl_enabled = 0;
}
return ret;
}
static lockdownd_error_t lockdownd_client_free_simple(lockdownd_client_t client)
{
if (!client)
return LOCKDOWN_E_INVALID_ARG;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
if (client->parent) {
if (property_list_service_client_free(client->parent) == PROPERTY_LIST_SERVICE_E_SUCCESS) {
ret = LOCKDOWN_E_SUCCESS;
}
}
if (client->session_id) {
free(client->session_id);
client->session_id = NULL;
}
if (client->udid) {
free(client->udid);
}
if (client->label) {
free(client->label);
}
free(client);
client = NULL;
return ret;
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_client_free(lockdownd_client_t client)
{
if (!client)
return LOCKDOWN_E_INVALID_ARG;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
if (client->session_id) {
lockdownd_stop_session(client, client->session_id);
}
ret = lockdownd_client_free_simple(client);
return ret;
}
LIBIMOBILEDEVICE_API void lockdownd_client_set_label(lockdownd_client_t client, const char *label)
{
if (client) {
if (client->label)
free(client->label);
client->label = (label != NULL) ? strdup(label): NULL;
}
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_receive(lockdownd_client_t client, plist_t *plist)
{
if (!client || !plist || (plist && *plist))
return LOCKDOWN_E_INVALID_ARG;
return lockdownd_error(property_list_service_receive_plist(client->parent, plist));
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_send(lockdownd_client_t client, plist_t plist)
{
if (!client || !plist)
return LOCKDOWN_E_INVALID_ARG;
return lockdownd_error(property_list_service_send_xml_plist(client->parent, plist));
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_query_type(lockdownd_client_t client, char **type)
{
if (!client)
return LOCKDOWN_E_INVALID_ARG;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t dict = plist_new_dict();
plist_dict_add_label(dict, client->label);
plist_dict_set_item(dict,"Request", plist_new_string("QueryType"));
debug_info("called");
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
ret = lockdownd_receive(client, &dict);
if (LOCKDOWN_E_SUCCESS != ret)
return ret;
ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t type_node = plist_dict_get_item(dict, "Type");
if (type_node && (plist_get_node_type(type_node) == PLIST_STRING)) {
char* typestr = NULL;
plist_get_string_val(type_node, &typestr);
debug_info("success with type %s", typestr);
/* return the type if requested */
if (type != NULL) {
*type = typestr;
} else {
free(typestr);
}
ret = LOCKDOWN_E_SUCCESS;
} else {
debug_info("hmm. QueryType response does not contain a type?!");
debug_plist(dict);
}
plist_free(dict);
dict = NULL;
return ret;
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_get_value(lockdownd_client_t client, const char *domain, const char *key, plist_t *value)
{
if (!client)
return LOCKDOWN_E_INVALID_ARG;
plist_t dict = NULL;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
/* setup request plist */
dict = plist_new_dict();
plist_dict_add_label(dict, client->label);
if (domain) {
plist_dict_set_item(dict,"Domain", plist_new_string(domain));
}
if (key) {
plist_dict_set_item(dict,"Key", plist_new_string(key));
}
plist_dict_set_item(dict,"Request", plist_new_string("GetValue"));
/* send to device */
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
if (ret != LOCKDOWN_E_SUCCESS)
return ret;
/* Now get device's answer */
ret = lockdownd_receive(client, &dict);
if (ret != LOCKDOWN_E_SUCCESS)
return ret;
ret = lockdown_check_result(dict, "GetValue");
if (ret == LOCKDOWN_E_SUCCESS) {
debug_info("success");
}
if (ret != LOCKDOWN_E_SUCCESS) {
plist_free(dict);
return ret;
}
plist_t value_node = plist_dict_get_item(dict, "Value");
if (value_node) {
debug_info("has a value");
*value = plist_copy(value_node);
}
plist_free(dict);
return ret;
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_set_value(lockdownd_client_t client, const char *domain, const char *key, plist_t value)
{
if (!client || !value)
return LOCKDOWN_E_INVALID_ARG;
plist_t dict = NULL;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
/* setup request plist */
dict = plist_new_dict();
plist_dict_add_label(dict, client->label);
if (domain) {
plist_dict_set_item(dict,"Domain", plist_new_string(domain));
}
if (key) {
plist_dict_set_item(dict,"Key", plist_new_string(key));
}
plist_dict_set_item(dict,"Request", plist_new_string("SetValue"));
plist_dict_set_item(dict,"Value", value);
/* send to device */
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
if (ret != LOCKDOWN_E_SUCCESS)
return ret;
/* Now get device's answer */
ret = lockdownd_receive(client, &dict);
if (ret != LOCKDOWN_E_SUCCESS)
return ret;
ret = lockdown_check_result(dict, "SetValue");
if (ret == LOCKDOWN_E_SUCCESS) {
debug_info("success");
}
if (ret != LOCKDOWN_E_SUCCESS) {
plist_free(dict);
return ret;
}
plist_free(dict);
return ret;
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_remove_value(lockdownd_client_t client, const char *domain, const char *key)
{
if (!client)
return LOCKDOWN_E_INVALID_ARG;
plist_t dict = NULL;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
/* setup request plist */
dict = plist_new_dict();
plist_dict_add_label(dict, client->label);
if (domain) {
plist_dict_set_item(dict,"Domain", plist_new_string(domain));
}
if (key) {
plist_dict_set_item(dict,"Key", plist_new_string(key));
}
plist_dict_set_item(dict,"Request", plist_new_string("RemoveValue"));
/* send to device */
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
if (ret != LOCKDOWN_E_SUCCESS)
return ret;
/* Now get device's answer */
ret = lockdownd_receive(client, &dict);
if (ret != LOCKDOWN_E_SUCCESS)
return ret;
ret = lockdown_check_result(dict, "RemoveValue");
if (ret == LOCKDOWN_E_SUCCESS) {
debug_info("success");
}
if (ret != LOCKDOWN_E_SUCCESS) {
plist_free(dict);
return ret;
}
plist_free(dict);
return ret;
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_get_device_udid(lockdownd_client_t client, char **udid)
{
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t value = NULL;
ret = lockdownd_get_value(client, NULL, "UniqueDeviceID", &value);
if (ret != LOCKDOWN_E_SUCCESS) {
return ret;
}
plist_get_string_val(value, udid);
plist_free(value);
value = NULL;
return ret;
}
/**
* Retrieves the public key of the device from lockdownd.
*
* @param client An initialized lockdownd client.
* @param public_key Holds the public key of the device. The caller is
* responsible for freeing the memory.
*
* @return LOCKDOWN_E_SUCCESS on success
*/
static lockdownd_error_t lockdownd_get_device_public_key_as_key_data(lockdownd_client_t client, key_data_t *public_key)
{
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t value = NULL;
char *value_value = NULL;
uint64_t size = 0;
ret = lockdownd_get_value(client, NULL, "DevicePublicKey", &value);
if (ret != LOCKDOWN_E_SUCCESS) {
return ret;
}
plist_get_data_val(value, &value_value, &size);
public_key->data = (unsigned char*)value_value;
public_key->size = size;
plist_free(value);
value = NULL;
return ret;
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_get_device_name(lockdownd_client_t client, char **device_name)
{
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t value = NULL;
ret = lockdownd_get_value(client, NULL, "DeviceName", &value);
if (ret != LOCKDOWN_E_SUCCESS) {
return ret;
}
plist_get_string_val(value, device_name);
plist_free(value);
value = NULL;
return ret;
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_client_new(idevice_t device, lockdownd_client_t *client, const char *label)
{
if (!device || !client)
return LOCKDOWN_E_INVALID_ARG;
static struct lockdownd_service_descriptor service = {
.port = 0xf27e,
.ssl_enabled = 0
};
property_list_service_client_t plistclient = NULL;
if (property_list_service_client_new(device, (lockdownd_service_descriptor_t)&service, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) {
debug_info("could not connect to lockdownd (device %s)", device->udid);
return LOCKDOWN_E_MUX_ERROR;
}
lockdownd_client_t client_loc = (lockdownd_client_t) malloc(sizeof(struct lockdownd_client_private));
client_loc->parent = plistclient;
client_loc->ssl_enabled = 0;
client_loc->session_id = NULL;
client_loc->mux_id = device->mux_id;
if (idevice_get_udid(device, &client_loc->udid) != IDEVICE_E_SUCCESS) {
debug_info("failed to get device udid.");
}
debug_info("device udid: %s", client_loc->udid);
client_loc->label = label ? strdup(label) : NULL;
*client = client_loc;
return LOCKDOWN_E_SUCCESS;
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_client_new_with_handshake(idevice_t device, lockdownd_client_t *client, const char *label)
{
if (!client)
return LOCKDOWN_E_INVALID_ARG;
lockdownd_error_t ret = LOCKDOWN_E_SUCCESS;
lockdownd_client_t client_loc = NULL;
plist_t pair_record = NULL;
char *host_id = NULL;
char *type = NULL;
ret = lockdownd_client_new(device, &client_loc, label);
if (LOCKDOWN_E_SUCCESS != ret) {
debug_info("failed to create lockdownd client.");
return ret;
}
/* perform handshake */
ret = lockdownd_query_type(client_loc, &type);
if (LOCKDOWN_E_SUCCESS != ret) {
debug_info("QueryType failed in the lockdownd client.");
} else if (strcmp("com.apple.mobile.lockdown", type)) {
debug_info("Warning QueryType request returned \"%s\".", type);
}
free(type);
if (device->version == 0) {
plist_t p_version = NULL;
if (lockdownd_get_value(client_loc, 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) {
device->version = DEVICE_VERSION(vers[0], vers[1], vers[2]);
}
free(s_version);
}
plist_free(p_version);
}
userpref_read_pair_record(client_loc->udid, &pair_record);
if (pair_record) {
pair_record_get_host_id(pair_record, &host_id);
}
if (LOCKDOWN_E_SUCCESS == ret && pair_record && !host_id) {
ret = LOCKDOWN_E_INVALID_CONF;
}
if (LOCKDOWN_E_SUCCESS == ret && !pair_record) {
/* attempt pairing */
ret = lockdownd_pair(client_loc, NULL);
}
plist_free(pair_record);
pair_record = NULL;
if (device->version < DEVICE_VERSION(7,0,0)) {
/* for older devices, we need to validate pairing to receive trusted host status */
ret = lockdownd_validate_pair(client_loc, NULL);
/* if not paired yet, let's do it now */
if (LOCKDOWN_E_INVALID_HOST_ID == ret) {
free(host_id);
host_id = NULL;
ret = lockdownd_pair(client_loc, NULL);
if (LOCKDOWN_E_SUCCESS == ret) {
ret = lockdownd_validate_pair(client_loc, NULL);
}
}
}
if (LOCKDOWN_E_SUCCESS == ret) {
if (!host_id) {
userpref_read_pair_record(client_loc->udid, &pair_record);
if (pair_record) {
pair_record_get_host_id(pair_record, &host_id);
plist_free(pair_record);
}
}
ret = lockdownd_start_session(client_loc, host_id, NULL, NULL);
if (LOCKDOWN_E_SUCCESS != ret) {
debug_info("Session opening failed.");
}
}
if (LOCKDOWN_E_SUCCESS == ret) {
*client = client_loc;
} else {
lockdownd_client_free(client_loc);
}
free(host_id);
return ret;
}
/**
* Returns a new plist from the supplied lockdownd pair record. The caller is
* responsible for freeing the plist.
*
* @param pair_record The pair record to create a plist from.
*
* @return A pair record plist from the device, NULL if pair_record is not set
*/
static plist_t lockdownd_pair_record_to_plist(lockdownd_pair_record_t pair_record)
{
if (!pair_record)
return NULL;
/* setup request plist */
plist_t dict = plist_new_dict();
plist_dict_set_item(dict, "DeviceCertificate", plist_new_data(pair_record->device_certificate, strlen(pair_record->device_certificate)));
plist_dict_set_item(dict, "HostCertificate", plist_new_data(pair_record->host_certificate, strlen(pair_record->host_certificate)));
plist_dict_set_item(dict, "HostID", plist_new_string(pair_record->host_id));
plist_dict_set_item(dict, "RootCertificate", plist_new_data(pair_record->root_certificate, strlen(pair_record->root_certificate)));
plist_dict_set_item(dict, "SystemBUID", plist_new_string(pair_record->system_buid));
return dict;
}
/**
* Generates a pair record plist with required certificates for a specific
* device. If a pairing exists, it is loaded from the computer instead of being
* generated.
*
* @param pair_record_plist Holds the pair record.
*
* @return LOCKDOWN_E_SUCCESS on success
*/
static lockdownd_error_t pair_record_generate(lockdownd_client_t client, plist_t *pair_record)
{
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
key_data_t public_key = { NULL, 0 };
char* host_id = NULL;
char* system_buid = NULL;
/* retrieve device public key */
ret = lockdownd_get_device_public_key_as_key_data(client, &public_key);
if (ret != LOCKDOWN_E_SUCCESS) {
debug_info("device refused to send public key.");
goto leave;
}
debug_info("device public key follows:\n%.*s", public_key.size, public_key.data);
*pair_record = plist_new_dict();
/* generate keys and certificates into pair record */
userpref_error_t uret = USERPREF_E_SUCCESS;
uret = pair_record_generate_keys_and_certs(*pair_record, public_key);
switch(uret) {
case USERPREF_E_INVALID_ARG:
ret = LOCKDOWN_E_INVALID_ARG;
break;
case USERPREF_E_INVALID_CONF:
ret = LOCKDOWN_E_INVALID_CONF;
break;
case USERPREF_E_SSL_ERROR:
ret = LOCKDOWN_E_SSL_ERROR;
default:
break;
}
/* set SystemBUID */
userpref_read_system_buid(&system_buid);
if (system_buid) {
plist_dict_set_item(*pair_record, USERPREF_SYSTEM_BUID_KEY, plist_new_string(system_buid));
}
/* set HostID */
host_id = generate_uuid();
pair_record_set_host_id(*pair_record, host_id);
leave:
if (host_id)
free(host_id);
if (system_buid)
free(system_buid);
if (public_key.data)
free(public_key.data);
return ret;
}
/**
* Function used internally by lockdownd_pair() and lockdownd_validate_pair()
*
* @param client The lockdown client
* @param pair_record The pair record to use for pairing. If NULL is passed, then
* the pair records from the current machine are used. New records will be
* generated automatically when pairing is done for the first time.
* @param verb This is either "Pair", "ValidatePair" or "Unpair".
* @param options The pairing options to pass.
* @param response If non-NULL a pointer to lockdownd's response dictionary is returned.
*
* @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL,
* LOCKDOWN_E_PLIST_ERROR if the pair_record certificates are wrong,
* LOCKDOWN_E_PAIRING_FAILED if the pairing failed,
* LOCKDOWN_E_PASSWORD_PROTECTED if the device is password protected,
* LOCKDOWN_E_INVALID_HOST_ID if the device does not know the caller's host id
*/
static lockdownd_error_t lockdownd_do_pair(lockdownd_client_t client, lockdownd_pair_record_t pair_record, const char *verb, plist_t options, plist_t *result)
{
if (!client)
return LOCKDOWN_E_INVALID_ARG;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t dict = NULL;
plist_t pair_record_plist = NULL;
plist_t wifi_node = NULL;
int pairing_mode = 0; /* 0 = libimobiledevice, 1 = external */
if (pair_record && pair_record->system_buid && pair_record->host_id) {
/* valid pair_record passed? */
if (!pair_record->device_certificate || !pair_record->host_certificate || !pair_record->root_certificate) {
return LOCKDOWN_E_PLIST_ERROR;
}
/* use passed pair_record */
pair_record_plist = lockdownd_pair_record_to_plist(pair_record);
pairing_mode = 1;
} else {
/* generate a new pair record if pairing */
if (!strcmp("Pair", verb)) {
ret = pair_record_generate(client, &pair_record_plist);
if (ret != LOCKDOWN_E_SUCCESS) {
if (pair_record_plist)
plist_free(pair_record_plist);
return ret;
}
/* get wifi mac now, if we get it later we fail on iOS 7 which causes a reconnect */
lockdownd_get_value(client, NULL, "WiFiAddress", &wifi_node);
} else {
/* use existing pair record */
userpref_read_pair_record(client->udid, &pair_record_plist);
if (!pair_record_plist) {
return LOCKDOWN_E_INVALID_HOST_ID;
}
}
}
plist_t request_pair_record = plist_copy(pair_record_plist);
/* remove stuff that is private */
plist_dict_remove_item(request_pair_record, USERPREF_ROOT_PRIVATE_KEY_KEY);
plist_dict_remove_item(request_pair_record, USERPREF_HOST_PRIVATE_KEY_KEY);
/* setup pair request plist */
dict = plist_new_dict();
plist_dict_add_label(dict, client->label);
plist_dict_set_item(dict, "PairRecord", request_pair_record);
plist_dict_set_item(dict, "Request", plist_new_string(verb));
plist_dict_set_item(dict, "ProtocolVersion", plist_new_string(LOCKDOWN_PROTOCOL_VERSION));
if (options) {
plist_dict_set_item(dict, "PairingOptions", plist_copy(options));
}
/* send to device */
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
if (ret != LOCKDOWN_E_SUCCESS) {
plist_free(pair_record_plist);
if (wifi_node)
plist_free(wifi_node);
return ret;
}
/* Now get device's answer */
ret = lockdownd_receive(client, &dict);
if (ret != LOCKDOWN_E_SUCCESS) {
plist_free(pair_record_plist);
if (wifi_node)
plist_free(wifi_node);
return ret;
}
if (strcmp(verb, "Unpair") == 0) {
/* workaround for Unpair giving back ValidatePair,
* seems to be a bug in the device's fw */
if (lockdown_check_result(dict, NULL) != LOCKDOWN_E_SUCCESS) {
ret = LOCKDOWN_E_PAIRING_FAILED;
}
} else {
if (lockdown_check_result(dict, verb) != LOCKDOWN_E_SUCCESS) {
ret = LOCKDOWN_E_PAIRING_FAILED;
}
}
/* if pairing succeeded */
if (ret == LOCKDOWN_E_SUCCESS) {
debug_info("%s success", verb);
if (!pairing_mode) {
debug_info("internal pairing mode");
if (!strcmp("Unpair", verb)) {
/* remove public key from config */
userpref_delete_pair_record(client->udid);
} else {
if (!strcmp("Pair", verb)) {
/* add returned escrow bag if available */
plist_t extra_node = plist_dict_get_item(dict, USERPREF_ESCROW_BAG_KEY);
if (extra_node && plist_get_node_type(extra_node) == PLIST_DATA) {
debug_info("Saving EscrowBag from response in pair record");
plist_dict_set_item(pair_record_plist, USERPREF_ESCROW_BAG_KEY, plist_copy(extra_node));
}
/* save previously retrieved wifi mac address in pair record */
if (wifi_node) {
debug_info("Saving WiFiAddress from device in pair record");
plist_dict_set_item(pair_record_plist, USERPREF_WIFI_MAC_ADDRESS_KEY, plist_copy(wifi_node));
plist_free(wifi_node);
wifi_node = NULL;
}
userpref_save_pair_record(client->udid, client->mux_id, pair_record_plist);
}
}
} else {
debug_info("external pairing mode");
}
} else {
debug_info("%s failure", verb);
plist_t error_node = NULL;
/* verify error condition */
error_node = plist_dict_get_item(dict, "Error");
if (error_node) {
char *value = NULL;
plist_get_string_val(error_node, &value);
if (value) {
/* the first pairing fails if the device is password protected */
ret = lockdownd_strtoerr(value);
free(value);
}
}
}
if (pair_record_plist) {
plist_free(pair_record_plist);
pair_record_plist = NULL;
}
if (wifi_node) {
plist_free(wifi_node);
wifi_node = NULL;
}
if (result) {
*result = dict;
} else {
plist_free(dict);
dict = NULL;
}
return ret;
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_pair(lockdownd_client_t client, lockdownd_pair_record_t pair_record)
{
plist_t options = plist_new_dict();
plist_dict_set_item(options, "ExtendedPairingErrors", plist_new_bool(1));
lockdownd_error_t ret = lockdownd_do_pair(client, pair_record, "Pair", options, NULL);
plist_free(options);
return ret;
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_pair_with_options(lockdownd_client_t client, lockdownd_pair_record_t pair_record, plist_t options, plist_t *response)
{
return lockdownd_do_pair(client, pair_record, "Pair", options, response);
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_validate_pair(lockdownd_client_t client, lockdownd_pair_record_t pair_record)
{
return lockdownd_do_pair(client, pair_record, "ValidatePair", NULL, NULL);
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_unpair(lockdownd_client_t client, lockdownd_pair_record_t pair_record)
{
return lockdownd_do_pair(client, pair_record, "Unpair", NULL, NULL);
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_enter_recovery(lockdownd_client_t client)
{
if (!client)
return LOCKDOWN_E_INVALID_ARG;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t dict = plist_new_dict();
plist_dict_add_label(dict, client->label);
plist_dict_set_item(dict,"Request", plist_new_string("EnterRecovery"));
debug_info("telling device to enter recovery mode");
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
ret = lockdownd_receive(client, &dict);
ret = lockdown_check_result(dict, "EnterRecovery");
if (ret == LOCKDOWN_E_SUCCESS) {
debug_info("success");
}
plist_free(dict);
dict = NULL;
return ret;
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_goodbye(lockdownd_client_t client)
{
if (!client)
return LOCKDOWN_E_INVALID_ARG;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t dict = plist_new_dict();
plist_dict_add_label(dict, client->label);
plist_dict_set_item(dict,"Request", plist_new_string("Goodbye"));
debug_info("called");
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
ret = lockdownd_receive(client, &dict);
if (!dict) {
debug_info("did not get goodbye response back");
return LOCKDOWN_E_PLIST_ERROR;
}
ret = lockdown_check_result(dict, "Goodbye");
if (ret == LOCKDOWN_E_SUCCESS) {
debug_info("success");
}
plist_free(dict);
dict = NULL;
return ret;
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_start_session(lockdownd_client_t client, const char *host_id, char **session_id, int *ssl_enabled)
{
lockdownd_error_t ret = LOCKDOWN_E_SUCCESS;
plist_t dict = NULL;
if (!client || !host_id)
ret = LOCKDOWN_E_INVALID_ARG;
/* if we have a running session, stop current one first */
if (client->session_id) {
lockdownd_stop_session(client, client->session_id);
}
/* setup request plist */
dict = plist_new_dict();
plist_dict_add_label(dict, client->label);
plist_dict_set_item(dict,"Request", plist_new_string("StartSession"));
/* add host id */
if (host_id) {
plist_dict_set_item(dict, "HostID", plist_new_string(host_id));
}
/* add system buid */
char *system_buid = NULL;
userpref_read_system_buid(&system_buid);
if (system_buid) {
plist_dict_set_item(dict, "SystemBUID", plist_new_string(system_buid));
if (system_buid) {
free(system_buid);
system_buid = NULL;
}
}
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
if (ret != LOCKDOWN_E_SUCCESS)
return ret;
ret = lockdownd_receive(client, &dict);
if (!dict)
return LOCKDOWN_E_PLIST_ERROR;
ret = lockdown_check_result(dict, "StartSession");
if (ret == LOCKDOWN_E_SUCCESS) {
uint8_t use_ssl = 0;
plist_t enable_ssl = plist_dict_get_item(dict, "EnableSessionSSL");
if (enable_ssl && (plist_get_node_type(enable_ssl) == PLIST_BOOLEAN)) {
plist_get_bool_val(enable_ssl, &use_ssl);
}
debug_info("Session startup OK");
if (ssl_enabled != NULL)
*ssl_enabled = use_ssl;
/* store session id, we need it for StopSession */
plist_t session_node = plist_dict_get_item(dict, "SessionID");
if (session_node && (plist_get_node_type(session_node) == PLIST_STRING)) {
plist_get_string_val(session_node, &client->session_id);
}
if (client->session_id) {
debug_info("SessionID: %s", client->session_id);
if (session_id != NULL)
*session_id = strdup(client->session_id);
} else {
debug_info("Failed to get SessionID!");
}
debug_info("Enable SSL Session: %s", (use_ssl ? "true" : "false"));
if (use_ssl) {
ret = lockdownd_error(property_list_service_enable_ssl(client->parent));
client->ssl_enabled = (ret == LOCKDOWN_E_SUCCESS ? 1 : 0);
} else {
ret = LOCKDOWN_E_SUCCESS;
client->ssl_enabled = 0;
}
}
plist_free(dict);
dict = NULL;
return ret;
}
/**
* Internal function used by lockdownd_do_start_service to create the
* StartService request's plist.
*
* @param client The lockdownd client
* @param identifier The identifier of the service to start
* @param send_escrow_bag Should we send the device's escrow bag with the request
* @param request The request's plist on success, NULL on failure
*
* @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_CONF on failure
* to read the escrow bag from the device's record (when used).
*/
static lockdownd_error_t lockdownd_build_start_service_request(lockdownd_client_t client, const char *identifier, int send_escrow_bag, plist_t *request)
{
plist_t dict = plist_new_dict();
/* create the basic request params */
plist_dict_add_label(dict, client->label);
plist_dict_set_item(dict, "Request", plist_new_string("StartService"));
plist_dict_set_item(dict, "Service", plist_new_string(identifier));
/* if needed - get the escrow bag for the device and send it with the request */
if (send_escrow_bag) {
/* get the pairing record */
plist_t pair_record = NULL;
userpref_read_pair_record(client->udid, &pair_record);
if (!pair_record) {
debug_info("ERROR: failed to read pair record for device: %s", client->udid);
plist_free(dict);
return LOCKDOWN_E_INVALID_CONF;
}
/* try to read the escrow bag from the record */
plist_t escrow_bag = plist_dict_get_item(pair_record, USERPREF_ESCROW_BAG_KEY);
if (!escrow_bag || (PLIST_DATA != plist_get_node_type(escrow_bag))) {
debug_info("ERROR: Failed to retrieve the escrow bag from the device's record");
plist_free(dict);
plist_free(pair_record);
return LOCKDOWN_E_INVALID_CONF;
}
debug_info("Adding escrow bag to StartService for %s", identifier);
plist_dict_set_item(dict, USERPREF_ESCROW_BAG_KEY, plist_copy(escrow_bag));
plist_free(pair_record);
}
*request = dict;
return LOCKDOWN_E_SUCCESS;
}
/**
* Function used internally by lockdownd_start_service and lockdownd_start_service_with_escrow_bag.
*
* @param client The lockdownd client
* @param identifier The identifier of the service to start
* @param send_escrow_bag Should we send the device's escrow bag with the request
* @param descriptor The service descriptor on success or NULL on failure
*
* @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG if a parameter
* is NULL, LOCKDOWN_E_INVALID_SERVICE if the requested service is not known
* by the device, LOCKDOWN_E_START_SERVICE_FAILED if the service could not because
* started by the device, LOCKDOWN_E_INVALID_CONF if the host id or escrow bag (when
* used) are missing from the device record.
*/
static lockdownd_error_t lockdownd_do_start_service(lockdownd_client_t client, const char *identifier, int send_escrow_bag, lockdownd_service_descriptor_t *service)
{
if (!client || !identifier || !service)
return LOCKDOWN_E_INVALID_ARG;
if (*service) {
// reset fields if service descriptor is reused
(*service)->port = 0;
(*service)->ssl_enabled = 0;
}
plist_t dict = NULL;
uint16_t port_loc = 0;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
/* create StartService request */
ret = lockdownd_build_start_service_request(client, identifier, send_escrow_bag, &dict);
if (LOCKDOWN_E_SUCCESS != ret)
return ret;
/* send to device */
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
if (LOCKDOWN_E_SUCCESS != ret)
return ret;
ret = lockdownd_receive(client, &dict);
if (LOCKDOWN_E_SUCCESS != ret)
return ret;
if (!dict)
return LOCKDOWN_E_PLIST_ERROR;
ret = lockdown_check_result(dict, "StartService");
if (ret == LOCKDOWN_E_SUCCESS) {
if (*service == NULL)
*service = (lockdownd_service_descriptor_t)malloc(sizeof(struct lockdownd_service_descriptor));
(*service)->port = 0;
(*service)->ssl_enabled = 0;
(*service)->identifier = strdup(identifier);
/* read service port number */
plist_t node = plist_dict_get_item(dict, "Port");
if (node && (plist_get_node_type(node) == PLIST_UINT)) {
uint64_t port_value = 0;
plist_get_uint_val(node, &port_value);
if (port_value) {
port_loc = port_value;
ret = LOCKDOWN_E_SUCCESS;
}
if (port_loc && ret == LOCKDOWN_E_SUCCESS) {
(*service)->port = port_loc;
}
}
/* check if the service requires SSL */
node = plist_dict_get_item(dict, "EnableServiceSSL");
if (node && (plist_get_node_type(node) == PLIST_BOOLEAN)) {
uint8_t b = 0;
plist_get_bool_val(node, &b);
(*service)->ssl_enabled = b;
}
} else {
plist_t error_node = plist_dict_get_item(dict, "Error");
if (error_node && PLIST_STRING == plist_get_node_type(error_node)) {
char *error = NULL;
plist_get_string_val(error_node, &error);
ret = lockdownd_strtoerr(error);
free(error);
}
}
plist_free(dict);
dict = NULL;
return ret;
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_start_service(lockdownd_client_t client, const char *identifier, lockdownd_service_descriptor_t *service)
{
return lockdownd_do_start_service(client, identifier, 0, service);
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_start_service_with_escrow_bag(lockdownd_client_t client, const char *identifier, lockdownd_service_descriptor_t *service)
{
return lockdownd_do_start_service(client, identifier, 1, service);
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_activate(lockdownd_client_t client, plist_t activation_record)
{
if (!client)
return LOCKDOWN_E_INVALID_ARG;
if (!client->session_id)
return LOCKDOWN_E_NO_RUNNING_SESSION;
if (!activation_record)
return LOCKDOWN_E_INVALID_ARG;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t dict = plist_new_dict();
plist_dict_add_label(dict, client->label);
plist_dict_set_item(dict,"Request", plist_new_string("Activate"));
plist_dict_set_item(dict,"ActivationRecord", plist_copy(activation_record));
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
ret = lockdownd_receive(client, &dict);
if (!dict) {
debug_info("LOCKDOWN_E_PLIST_ERROR");
return LOCKDOWN_E_PLIST_ERROR;
}
ret = lockdown_check_result(dict, "Activate");
if (ret == LOCKDOWN_E_SUCCESS) {
debug_info("success");
}
plist_free(dict);
dict = NULL;
return ret;
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_deactivate(lockdownd_client_t client)
{
if (!client)
return LOCKDOWN_E_INVALID_ARG;
if (!client->session_id)
return LOCKDOWN_E_NO_RUNNING_SESSION;
lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t dict = plist_new_dict();
plist_dict_add_label(dict, client->label);
plist_dict_set_item(dict,"Request", plist_new_string("Deactivate"));
ret = lockdownd_send(client, dict);
plist_free(dict);
dict = NULL;
ret = lockdownd_receive(client, &dict);
if (!dict) {
debug_info("LOCKDOWN_E_PLIST_ERROR");
return LOCKDOWN_E_PLIST_ERROR;
}
ret = lockdown_check_result(dict, "Deactivate");
if (ret == LOCKDOWN_E_SUCCESS) {
debug_info("success");
}
plist_free(dict);
dict = NULL;
return ret;
}
static void str_remove_spaces(char *source)
{
char *dest = source;
while (*source != 0) {
if (!isspace(*source)) {
*dest++ = *source; /* copy */
}
source++;
}
*dest = 0;
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_get_sync_data_classes(lockdownd_client_t client, char ***classes, int *count)
{
if (!client)
return LOCKDOWN_E_INVALID_ARG;
if (!client->session_id)
return LOCKDOWN_E_NO_RUNNING_SESSION;
plist_t dict = NULL;
lockdownd_error_t err = LOCKDOWN_E_UNKNOWN_ERROR;
plist_t value = NULL;
char **newlist = NULL;
char *val = NULL;
*classes = NULL;
*count = 0;
err = lockdownd_get_value(client, "com.apple.mobile.iTunes", "SyncDataClasses", &dict);
if (err != LOCKDOWN_E_SUCCESS) {
if (dict) {
plist_free(dict);
}
return err;
}
if (plist_get_node_type(dict) != PLIST_ARRAY) {
plist_free(dict);
return LOCKDOWN_E_PLIST_ERROR;
}
while((value = plist_array_get_item(dict, *count)) != NULL) {
plist_get_string_val(value, &val);
newlist = realloc(*classes, sizeof(char*) * (*count+1));
str_remove_spaces(val);
if (asprintf(&newlist[*count], "com.apple.%s", val) < 0) {
debug_info("ERROR: asprintf failed");
}
free(val);
val = NULL;
*classes = newlist;
*count = *count+1;
}
newlist = realloc(*classes, sizeof(char*) * (*count+1));
newlist[*count] = NULL;
*classes = newlist;
if (dict) {
plist_free(dict);
}
return LOCKDOWN_E_SUCCESS;
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_data_classes_free(char **classes)
{
if (classes) {
int i = 0;
while (classes[i++]) {
free(classes[i]);
}
free(classes);
}
return LOCKDOWN_E_SUCCESS;
}
LIBIMOBILEDEVICE_API lockdownd_error_t lockdownd_service_descriptor_free(lockdownd_service_descriptor_t service)
{
if (service) {
free(service->identifier);
free(service);
}
return LOCKDOWN_E_SUCCESS;
}
LIBIMOBILEDEVICE_API const char* lockdownd_strerror(lockdownd_error_t err)
{
switch (err) {
case LOCKDOWN_E_SUCCESS:
return "Success";
case LOCKDOWN_E_INVALID_ARG:
return "Invalid argument";
case LOCKDOWN_E_INVALID_CONF:
return "Invalid configuration";
case LOCKDOWN_E_PLIST_ERROR:
return "PropertyList error";
case LOCKDOWN_E_PAIRING_FAILED:
return "Pairing failed";
case LOCKDOWN_E_SSL_ERROR:
return "SSL error";
case LOCKDOWN_E_DICT_ERROR:
return "Invalid dictionary";
case LOCKDOWN_E_RECEIVE_TIMEOUT:
return "Receive timeout";
case LOCKDOWN_E_MUX_ERROR:
return "Mux error";
case LOCKDOWN_E_NO_RUNNING_SESSION:
return "No running session";
case LOCKDOWN_E_UNKNOWN_ERROR:
return "Unknown Error";
default: {
int i = 0;
while (lockdownd_error_str_map[i].lockdown_errstr) {
if (lockdownd_error_str_map[i].errcode == err) {
return lockdownd_error_str_map[i].errstr;
}
i++;
}
} break;
}
return "Unknown Error";
}