/*
 * 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 <libimobiledevice-glue/utils.h>

#include "property_list_service.h"
#include "lockdown.h"
#include "idevice.h"
#include "common/debug.h"
#include "common/userpref.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.
 */
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;
	}

	const char *query_value = plist_get_string_ptr(query_node, NULL);
	if (!query_value) {
		return ret;
	}

	if (query_match && (strcmp(query_value, query_match) != 0)) {
		return ret;
	}

	/* Check for 'Error' in reply */
	plist_t err_node = plist_dict_get_item(dict, "Error");
	if (err_node) {
		if (plist_get_node_type(err_node) == PLIST_STRING) {
			const char *err_value = plist_get_string_ptr(err_node, NULL);
			if (err_value) {
				debug_info("ERROR: %s", err_value);
				ret = lockdownd_strtoerr(err_value);
			} else {
				debug_info("ERROR: unknown error occurred");
			}
		}
		return ret;
	}

	plist_t result_node = plist_dict_get_item(dict, "Result");
	if (!result_node) {
		/* With iOS 5+ 'Result' is not present anymore.
		   If there is no 'Error', we can just assume success. */
		return LOCKDOWN_E_SUCCESS;
	}
	if (plist_get_node_type(result_node) == PLIST_STRING) {
		const char *result_value = plist_get_string_ptr(result_node, NULL);
		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);
			}
		}
	}

	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->label) {
		free(client->label);
	}
	if (client->cu_key) {
		free(client->cu_key);
		client->cu_key = NULL;
	}

	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->device = device;
	client_loc->cu_key = NULL;
	client_loc->cu_key_len = 0;

	if (device->udid) {
		debug_info("device udid: %s", device->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) != 0) {
		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);
	}
	if (device->device_class == 0) {
		plist_t p_device_class = NULL;
		if (lockdownd_get_value(client_loc, NULL, "DeviceClass", &p_device_class) == LOCKDOWN_E_SUCCESS) {
			char* s_device_class = NULL;
			plist_get_string_val(p_device_class, &s_device_class);
			if (s_device_class != NULL) {
				if (!strcmp(s_device_class, "iPhone")) {
					device->device_class = DEVICE_CLASS_IPHONE;
				} else if (!strcmp(s_device_class, "iPad")) {
					device->device_class = DEVICE_CLASS_IPAD;
				} else if (!strcmp(s_device_class, "iPod")) {
					device->device_class = DEVICE_CLASS_IPOD;
				} else if (!strcmp(s_device_class, "Watch")) {
					device->device_class = DEVICE_CLASS_WATCH;
				} else if (!strcmp(s_device_class, "AppleTV")) {
					device->device_class = DEVICE_CLASS_APPLETV;
				} else {
					device->device_class = DEVICE_CLASS_UNKNOWN;
				}
				free(s_device_class);
			}
		}
		plist_free(p_device_class);
	}

	userpref_error_t uerr = userpref_read_pair_record(client_loc->device->udid, &pair_record);
	if (uerr == USERPREF_E_READ_ERROR) {
		debug_info("ERROR: Failed to retrieve pair record for %s", client_loc->device->udid);
		lockdownd_client_free(client_loc);
		return LOCKDOWN_E_RECEIVE_TIMEOUT;
	}
	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 */
		free(host_id);
		host_id = NULL;
		ret = lockdownd_pair(client_loc, NULL);
	}

	plist_free(pair_record);
	pair_record = NULL;

	if (device->version < DEVICE_VERSION(7,0,0) && device->device_class != DEVICE_CLASS_WATCH) {
		/* 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) {
			uerr = userpref_read_pair_record(client_loc->device->udid, &pair_record);
			if (uerr == USERPREF_E_READ_ERROR) {
				debug_info("ERROR: Failed to retrieve pair record for %s", client_loc->device->udid);
				lockdownd_client_free(client_loc);
				return LOCKDOWN_E_RECEIVE_TIMEOUT;
			} else if (uerr == USERPREF_E_NOENT) {
				debug_info("ERROR: No pair record for %s", client_loc->device->udid);
				lockdownd_client_free(client_loc);
				return LOCKDOWN_E_INVALID_CONF;
			} else if (uerr != USERPREF_E_SUCCESS) {
				debug_info("ERROR: Failed to retrieve or parse pair record for %s", client_loc->device->udid);
				lockdownd_client_free(client_loc);
				return LOCKDOWN_E_INVALID_CONF;
			}
			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_error_t uerr = userpref_read_pair_record(client->device->udid, &pair_record_plist);
			if (uerr == USERPREF_E_READ_ERROR) {
				debug_info("ERROR: Failed to retrieve pair record for %s", client->device->udid);
				return LOCKDOWN_E_RECEIVE_TIMEOUT;
			} else if (uerr == USERPREF_E_NOENT) {
				debug_info("ERROR: No pair record for %s", client->device->udid);
				return LOCKDOWN_E_INVALID_CONF;
			} else if (uerr != USERPREF_E_SUCCESS) {
				debug_info("ERROR: Failed to retrieve or parse pair record for %s", client->device->udid);
				return LOCKDOWN_E_INVALID_CONF;
			}
		}
	}

	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->device->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->device->udid, client->device->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_error_t uerr = userpref_read_pair_record(client->device->udid, &pair_record);
		if (uerr == USERPREF_E_READ_ERROR) {
			debug_info("ERROR: Failed to retrieve pair record for %s", client->device->udid);
			plist_free(dict);
			return LOCKDOWN_E_RECEIVE_TIMEOUT;
		} else if (uerr == USERPREF_E_NOENT) {
			debug_info("ERROR: No pair record for %s", client->device->udid);
			plist_free(dict);
			return LOCKDOWN_E_INVALID_CONF;
		} else if (uerr != USERPREF_E_SUCCESS) {
			debug_info("ERROR: Failed to retrieve or parse pair record for %s", client->device->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";
}