diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Makefile.am | 10 | ||||
| -rw-r--r-- | src/activation.c | 1076 | 
2 files changed, 1086 insertions, 0 deletions
| diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..0f0d807 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,10 @@ +AM_CPPFLAGS = -I$(top_srcdir)/include + +AM_CFLAGS = $(GLOBAL_CFLAGS) $(libimobiledevice_CFLAGS) $(libplist_CFLAGS) $(libcurl_CFLAGS) $(libxml2_CFLAGS) +AM_LDFLAGS = $(GLOBAL_LIBS) $(libimobiledevice_LIBS) $(libplist_LIBS) $(libcurl_LIBS) $(libxml2_LIBS) + +lib_LTLIBRARIES = libideviceactivation.la +libideviceactivation_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LIBIDEVICEACTIVATION_SO_VERSION) -no-undefined +libideviceactivation_la_SOURCES = \ +		activation.c + diff --git a/src/activation.c b/src/activation.c new file mode 100644 index 0000000..40f423f --- /dev/null +++ b/src/activation.c @@ -0,0 +1,1076 @@ +/** + * @file activation.c + * + * Copyright (c) 2011-2014 Mirell Development, 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <libxml/parser.h> +#include <libxml/xpath.h> +#include <libxml/HTMLtree.h> +#include <curl/curl.h> + +#include <libideviceactivation.h> + +#define IDEVICE_ACTIVATION_USER_AGENT_IOS "iOS Device Activator (MobileActivation-20 built on Jan 15 2012 at 19:07:28)" +#define IDEVICE_ACTIVATION_USER_AGENT_ITUNES "iTunes/11.1.4 (Macintosh; OS X 10.9.1) AppleWebKit/537.73.11" +#define IDEVICE_ACTIVATION_DEFAULT_URL "https://albert.apple.com/deviceservices/deviceActivation" + +typedef enum { +	IDEVICE_ACTIVATION_CONTENT_TYPE_URL_ENCODED, +	IDEVICE_ACTIVATION_CONTENT_TYPE_MULTIPART_FORMDATA, +	IDEVICE_ACTIVATION_CONTENT_TYPE_HTML, +	IDEVICE_ACTIVATION_CONTENT_TYPE_BUDDYML, +	IDEVICE_ACTIVATION_CONTENT_TYPE_PLIST, +	IDEVICE_ACTIVATION_CONTENT_TYPE_UNKNOWN +} idevice_activation_content_type_t; + +struct idevice_activation_request_private { +	idevice_activation_client_type_t client_type; +	idevice_activation_content_type_t content_type; +	char* url; +	plist_t fields; +}; + +struct idevice_activation_response_private { +	char* raw_content; +	size_t raw_content_size; +	idevice_activation_content_type_t content_type; +	char* title; +	char* description; +	plist_t activation_record; +	plist_t fields; +	plist_t fields_require_input; +	plist_t labels; +	int is_activation_ack; +	int is_auth_required; +	int has_errors; +}; + +int debug_level = 0; + +void idevice_activation_set_debug_level(int level) { +	debug_level = level; +} + +static idevice_activation_error_t idevice_activation_activation_node_from_plist_xml(const char* plist_xml, size_t size, plist_t* activation_node) +{ +	plist_t activation_ticket = NULL; + +	plist_from_xml(plist_xml, size, &activation_ticket); +	if (activation_ticket == NULL) { +		return IDEVICE_ACTIVATION_E_PLIST_PARSING_ERROR; +	} + +	plist_t activation_node_tmp = plist_dict_get_item(activation_ticket, "iphone-activation"); +	if (!activation_node_tmp) { +		activation_node_tmp = plist_dict_get_item(activation_ticket, "device-activation"); +		if (!activation_node_tmp) { +			plist_free(activation_ticket); +			return IDEVICE_ACTIVATION_E_PLIST_PARSING_ERROR; +		} +	} + +	*activation_node = plist_copy(activation_node_tmp); +	plist_free(activation_ticket); + +	return IDEVICE_ACTIVATION_E_SUCCESS; +} + +static idevice_activation_error_t idevice_activation_activation_record_from_activation_node(plist_t activation_node, plist_t* activation_record) +{ +	plist_t record = plist_dict_get_item(activation_node, "activation-record"); +	if (!record) { +		return IDEVICE_ACTIVATION_E_PLIST_PARSING_ERROR; +	} + +	*activation_record = plist_copy(record); + +	return IDEVICE_ACTIVATION_E_SUCCESS; +} + +static void idevice_activation_response_add_field(idevice_activation_response_t response, const char* key, const char* value, int required_input) +{ +	plist_dict_set_item(response->fields, key, plist_new_string(value)); +	if (required_input) { +		plist_dict_set_item(response->fields_require_input, key, plist_new_bool(1)); +	} +} + +static idevice_activation_error_t idevice_activation_parse_buddyml_response(idevice_activation_response_t response) +{ +	idevice_activation_error_t result = IDEVICE_ACTIVATION_E_SUCCESS; +	xmlDocPtr doc = NULL; +	xmlXPathContextPtr context = NULL; +	xmlXPathObjectPtr xpath_result = NULL; +	int i = 0; + +	if (!response->content_type == IDEVICE_ACTIVATION_CONTENT_TYPE_BUDDYML) +		return IDEVICE_ACTIVATION_E_UNKNOWN_CONTENT_TYPE; + +	doc = xmlReadMemory(response->raw_content, response->raw_content_size, "ideviceactivation.xml", NULL, XML_PARSE_NOERROR); +	if (!doc) { +		result = IDEVICE_ACTIVATION_E_BUDDYML_PARSING_ERROR; +		goto cleanup; +	} + +	context = xmlXPathNewContext(doc); +	if (!context) { +		result = IDEVICE_ACTIVATION_E_BUDDYML_PARSING_ERROR; +		goto cleanup; +	} + +	// check for an error +	// <navigationBar> appears directly under <xmlui> only in case of an error +	xpath_result = xmlXPathEvalExpression((const xmlChar*) "/xmlui/navigationBar/@title", context); +	if (!xpath_result) { +		result = IDEVICE_ACTIVATION_E_INTERNAL_ERROR; +		goto cleanup; +	} + +	if (xpath_result->nodesetval && xpath_result->nodesetval->nodeNr) { +		xmlChar* content =  xmlNodeListGetString(doc, xpath_result->nodesetval->nodeTab[0]->xmlChildrenNode, 1); +		if (content) { +			response->title = strdup((const char*) content); +			xmlFree(content); +		} + +		response->has_errors = 1; +		goto cleanup; +	} + +	// check for activation ack +	if (xpath_result) { +		xmlXPathFreeObject(xpath_result); +		xpath_result = NULL; +	} +	xpath_result = xmlXPathEvalExpression((const xmlChar*) "/xmlui/clientInfo[@ack-received='true']", context); +	if (!xpath_result) { +		result = IDEVICE_ACTIVATION_E_INTERNAL_ERROR; +		goto cleanup; +	} + +	if (xpath_result->nodesetval && xpath_result->nodesetval->nodeNr) { +		// existing activation_acknowledged +		response->is_activation_ack = 1; +		goto cleanup; +	} + +	// retrieve response title +	if (xpath_result) { +		xmlXPathFreeObject(xpath_result); +		xpath_result = NULL; +	} +	xpath_result = xmlXPathEvalExpression((const xmlChar*) "/xmlui/alert/@title", context); +	if (!xpath_result) { +		result = IDEVICE_ACTIVATION_E_INTERNAL_ERROR; +		goto cleanup; +	} + +	if (xpath_result->nodesetval && xpath_result->nodesetval->nodeNr) { +		// incorrect credentials +		// <alert> exists only in case of incorrect credentials +		xmlChar* content =  xmlNodeListGetString(doc, xpath_result->nodesetval->nodeTab[0]->xmlChildrenNode, 1); +		if (content) { +			response->title = strdup((const char*) content); +			xmlFree(content); +		} +	} else { +		if (xpath_result) { +			xmlXPathFreeObject(xpath_result); +			xpath_result = NULL; +		} +		xpath_result = xmlXPathEvalExpression((const xmlChar*) "/xmlui/page/navigationBar/@title", context); +		if (!xpath_result) { +			result = IDEVICE_ACTIVATION_E_INTERNAL_ERROR; +			goto cleanup; +		} + +		if (!xpath_result->nodesetval) { +			result =  IDEVICE_ACTIVATION_E_BUDDYML_PARSING_ERROR; +			goto cleanup; +		} +		xmlChar* content =  xmlNodeListGetString(doc, xpath_result->nodesetval->nodeTab[0]->xmlChildrenNode, 1); +		if (content) { +			response->title = strdup((const char*) content); +			xmlFree(content); +		} +	} + +	// retrieve response description +	if (xpath_result) { +		xmlXPathFreeObject(xpath_result); +		xpath_result = NULL; +	} +	xpath_result = xmlXPathEvalExpression((const xmlChar*) "/xmlui/page/tableView/section[@footer and not(@footerLinkURL)]/@footer", context); +	if (!xpath_result) { +		result = IDEVICE_ACTIVATION_E_INTERNAL_ERROR; +		goto cleanup; +	} + +	if (xpath_result->nodesetval) { +		char* response_description = (char*) malloc(sizeof(char)); +		response_description[0] = '\0'; + +		for(i = 0; i < xpath_result->nodesetval->nodeNr; i++) { +			xmlChar* content = xmlNodeListGetString(doc, xpath_result->nodesetval->nodeTab[i]->xmlChildrenNode, 1); +			if (content) { +				const size_t len = strlen(response_description); +				response_description = (char*) realloc(response_description, len + xmlStrlen(content) + 2); +				sprintf(&response_description[len], "%s\n", (const char*) content); +				xmlFree(content); +			} +		} + +		if (strlen(response_description) > 0) { +			// remove the last '\n' +			response_description[strlen(response_description) - 1] = '\0'; +			response->description = response_description; +		} else { +			free(response_description); +		} +	} + +	// retrieve input fields +	if (xpath_result) { +		xmlXPathFreeObject(xpath_result); +		xpath_result = NULL; +	} +	xpath_result = xmlXPathEvalExpression((const xmlChar*) "/xmlui/page//editableTextRow", context); +	if (!xpath_result) { +		result = IDEVICE_ACTIVATION_E_INTERNAL_ERROR; +		goto cleanup; +	} + +	if (xpath_result->nodesetval) { +		for(i = 0; i < xpath_result->nodesetval->nodeNr; i++) { +			xmlChar* id = xmlGetProp(xpath_result->nodesetval->nodeTab[i], (const xmlChar*) "id"); +			if (!id) { +				result = IDEVICE_ACTIVATION_E_BUDDYML_PARSING_ERROR; +				goto cleanup; +			} + +			idevice_activation_response_add_field(response, (const char*) id, "", 1); + +			xmlChar* label = xmlGetProp(xpath_result->nodesetval->nodeTab[i], (const xmlChar*) "label"); +			if (label) { +				plist_dict_set_item(response->labels, (const char*)id, plist_new_string((const char*) label)); +				xmlFree(label); +			} + +			xmlFree(id); +		} +	} + +	// retrieve server info fields +	if (xpath_result) { +		xmlXPathFreeObject(xpath_result); +		xpath_result = NULL; +	} + +	xpath_result = xmlXPathEvalExpression((const xmlChar*) "/xmlui/serverInfo/@*", context); +	if (!xpath_result) { +		result = IDEVICE_ACTIVATION_E_INTERNAL_ERROR; +		goto cleanup; +	} + +	if (xpath_result->nodesetval) { +		for(i = 0; i < xpath_result->nodesetval->nodeNr; i++) { +			xmlChar* content = xmlNodeGetContent(xpath_result->nodesetval->nodeTab[i]); +			if (content) { +				if (!xmlStrcmp(xpath_result->nodesetval->nodeTab[i]->name, (const xmlChar*) "isAuthRequired")) { +					response->is_auth_required = 1; +				} + +				idevice_activation_response_add_field(response, +					(const char*) xpath_result->nodesetval->nodeTab[i]->name, (const char*) content, 0); +				xmlFree(content); +			} +		} +	} + +	if (plist_dict_get_size(response->fields) == 0) { +		response->has_errors = 1; +	} + +cleanup: +	if (xpath_result) +		xmlXPathFreeObject(xpath_result); +	if (context) +		xmlXPathFreeContext(context); +	if (doc) +		xmlFreeDoc(doc); + +	return result; +} + +static idevice_activation_error_t idevice_activation_parse_html_response(idevice_activation_response_t response) +{ +	idevice_activation_error_t result = IDEVICE_ACTIVATION_E_SUCCESS; +	xmlDocPtr doc = NULL; +	xmlXPathContextPtr context = NULL; +	xmlXPathObjectPtr xpath_result = NULL; + +	if (!response->content_type == IDEVICE_ACTIVATION_CONTENT_TYPE_HTML) +		return IDEVICE_ACTIVATION_E_UNKNOWN_CONTENT_TYPE; + +	doc = xmlReadMemory(response->raw_content, response->raw_content_size, "ideviceactivation.xml", NULL, XML_PARSE_RECOVER | XML_PARSE_NOERROR); +	if (!doc) { +		result = IDEVICE_ACTIVATION_E_HTML_PARSING_ERROR; +		goto cleanup; +	} + +	context = xmlXPathNewContext(doc); +	if (!context) { +		result = IDEVICE_ACTIVATION_E_HTML_PARSING_ERROR; +		goto cleanup; +	} + +	// check for authentication required +	if (xpath_result) { +		xmlXPathFreeObject(xpath_result); +		xpath_result = NULL; +	} +	xpath_result = xmlXPathEvalExpression((const xmlChar*) "//input[@name='isAuthRequired' and @value='true']", context); +	if (!xpath_result) { +		result = IDEVICE_ACTIVATION_E_INTERNAL_ERROR; +		goto cleanup; +	} + +	if (xpath_result->nodesetval && xpath_result->nodesetval->nodeNr) { +		response->is_auth_required = 1; +		goto cleanup; +	} + +	// check for plist content +	xpath_result = xmlXPathEvalExpression((const xmlChar*) "//script[@type='text/x-apple-plist']/plist", context); +	if (!xpath_result) { +		result = IDEVICE_ACTIVATION_E_INTERNAL_ERROR; +		goto cleanup; +	} + +	if (xpath_result->nodesetval && xpath_result->nodesetval->nodeNr) { +		plist_t activation_node = NULL; +		xmlBufferPtr plistNodeBuffer = xmlBufferCreate(); +		if (htmlNodeDump(plistNodeBuffer, doc, xpath_result->nodesetval->nodeTab[0]) == -1) { +			result = IDEVICE_ACTIVATION_E_HTML_PARSING_ERROR; +			goto local_cleanup; +		} + +		result = idevice_activation_activation_node_from_plist_xml( +			(const char*) plistNodeBuffer->content, plistNodeBuffer->use, &activation_node); +		if (result != IDEVICE_ACTIVATION_E_SUCCESS) { +			response->has_errors = 1; +			result = IDEVICE_ACTIVATION_E_SUCCESS; +			goto local_cleanup; +		} + +		// check for ack-received +		plist_t ack_received = plist_dict_get_item(activation_node, "ack-received"); +		if (ack_received) { +			uint8_t val = 0; +			plist_get_bool_val(ack_received, &val); +			if (val) { +				response->is_activation_ack = 1; +			} else { +				result = IDEVICE_ACTIVATION_E_PLIST_PARSING_ERROR; +			} + +			goto local_cleanup; +		} + +		// try to retrieve the activation record +		result = idevice_activation_activation_record_from_activation_node(activation_node, &response->activation_record); +		if (result != IDEVICE_ACTIVATION_E_SUCCESS) { +			goto local_cleanup; +		} + +	local_cleanup: +		if (plistNodeBuffer) +			xmlBufferFree(plistNodeBuffer); +		if (activation_node) +			plist_free(activation_node); +		goto cleanup; +	} + +	response->has_errors = 1; + +cleanup: +	if (xpath_result) +		xmlXPathFreeObject(xpath_result); +	if (context) +		xmlXPathFreeContext(context); +	if (doc) +		xmlFreeDoc(doc); + +	return result; +} + +static idevice_activation_error_t idevice_activation_parse_raw_response(idevice_activation_response_t response) +{ +	switch(response->content_type) +	{ +		case IDEVICE_ACTIVATION_CONTENT_TYPE_PLIST: +		{ +			plist_t activation_node = NULL; +			idevice_activation_error_t result = IDEVICE_ACTIVATION_E_SUCCESS; + +			result = idevice_activation_activation_node_from_plist_xml(response->raw_content, response->raw_content_size, &activation_node); +			if (result != IDEVICE_ACTIVATION_E_SUCCESS) { +				return result; +			} + +			result = idevice_activation_activation_record_from_activation_node(activation_node, &response->activation_record); +			plist_free(activation_node); +			return result; +		} +		case IDEVICE_ACTIVATION_CONTENT_TYPE_BUDDYML: +			return idevice_activation_parse_buddyml_response(response); +		case IDEVICE_ACTIVATION_CONTENT_TYPE_HTML: +			return idevice_activation_parse_html_response(response); +		default: +			return IDEVICE_ACTIVATION_E_UNKNOWN_CONTENT_TYPE; +	} + +	return IDEVICE_ACTIVATION_E_SUCCESS; +} + +static size_t idevice_activation_write_callback(char* data, size_t size, size_t nmemb, void* userdata) +{ +	idevice_activation_response_t response = (idevice_activation_response_t)userdata; +	const size_t total = size * nmemb; + +	if (total != 0) { +		response->raw_content = realloc(response->raw_content, response->raw_content_size + total); +		memcpy(response->raw_content + response->raw_content_size, data, total); +		response->raw_content_size += total; +	} + +	return total; +} + +static size_t idevice_activation_header_callback(void *data, size_t size, size_t nmemb, void *userdata) +{ +	idevice_activation_response_t response = (idevice_activation_response_t)userdata; +	const size_t total = size * nmemb; +	if (total != 0) { +		if (strstr(data, "Content-Type: text/xml") != NULL) { +			response->content_type = IDEVICE_ACTIVATION_CONTENT_TYPE_PLIST; +		} else if (strstr(data, "Content-Type: application/x-buddyml") != NULL) { +			response->content_type = IDEVICE_ACTIVATION_CONTENT_TYPE_BUDDYML; +		} else if (strstr(data, "Content-Type: text/html") != NULL) { +			response->content_type = IDEVICE_ACTIVATION_CONTENT_TYPE_HTML; +		} +	} +	return total; +} + +static char* urlencode(const char* buf) +{ +	static const signed char conv_table[256] = { +		1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +		1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, +		1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, +		1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, +		1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, +		1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +		1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +		1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +		1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +		1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +		1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +		1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +		1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 +	}; + +	unsigned int i; +	int count = 0; +	for (i = 0; i < strlen(buf); i++) { +		if (conv_table[(int)buf[i]]) { +			count++; +		} +	} +	int newsize = strlen(buf) + count*2 + 1; +	char* res = malloc(newsize); +	int o = 0; +	for (i = 0; i < strlen(buf); i++) { +		if (conv_table[(int)buf[i]]) { +			sprintf(&res[o], "%%%02X", (unsigned char)buf[i]); +			o+=3; +		} else { +			res[o] = buf[i]; +			o++; +		} +	} +	res[o] = '\0'; + +	return res; +} + +static int plist_strip_xml(char** xmlplist) +{ +	uint32_t size = 0; + +	if (!xmlplist && !*xmlplist) +		return -1; + +	char* start = strstr(*xmlplist, "<plist version=\"1.0\">\n"); +	if (start == NULL) { +		return -1; +	} + +	char* stop = strstr(*xmlplist, "\n</plist>"); +	if (stop == NULL) { +		return -1; +	} + +	start += strlen("<plist version=\"1.0\">\n"); +	size = stop - start; +	char* stripped = malloc(size + 1); +	memset(stripped, '\0', size + 1); +	memcpy(stripped, start, size); +	free(*xmlplist); +	*xmlplist = stripped; +	stripped = NULL; + +	return 0; +} + +idevice_activation_error_t idevice_activation_request_new(idevice_activation_client_type_t client_type, idevice_activation_request_t* request) +{ +	idevice_activation_request_t tmp_request = (idevice_activation_request_t) malloc(sizeof(idevice_activation_request)); + +	if (!tmp_request) { +		return IDEVICE_ACTIVATION_E_OUT_OF_MEMORY; +	} + +	tmp_request->client_type = client_type; +	tmp_request->content_type = IDEVICE_ACTIVATION_CONTENT_TYPE_URL_ENCODED; +	tmp_request->url = strdup(IDEVICE_ACTIVATION_DEFAULT_URL); +	tmp_request->fields = plist_new_dict(); +	*request = tmp_request; + +	return IDEVICE_ACTIVATION_E_SUCCESS; +} + +idevice_activation_error_t idevice_activation_request_new_from_lockdownd(idevice_activation_client_type_t client_type, lockdownd_client_t lockdown, idevice_activation_request** request) +{ +	// check arguments +	if (!lockdown) { +		return IDEVICE_ACTIVATION_E_INTERNAL_ERROR; +	} + +	plist_t node = NULL; +	plist_t fields = plist_new_dict(); + +	// add InStoreActivation +	plist_dict_set_item(fields, "InStoreActivation", plist_new_string("false")); + +	// add AppleSerialNumber +	if ((lockdownd_get_value(lockdown, NULL, "SerialNumber", &node) != LOCKDOWN_E_SUCCESS) || !node || (plist_get_node_type(node) != PLIST_STRING)) { +		fprintf(stderr, "%s: Unable to get SerialNumber from lockdownd\n", __func__); +		plist_free(fields); +		return IDEVICE_ACTIVATION_E_INCOMPLETE_INFO; +	} else { +		plist_dict_set_item(fields, "AppleSerialNumber", plist_copy(node)); +	} +	if (node) { +		plist_free(node); +		node = NULL; +	} + +	// add IMEI +	if ((lockdownd_get_value(lockdown, NULL, "InternationalMobileEquipmentIdentity", &node) != LOCKDOWN_E_SUCCESS) || !node || (plist_get_node_type(node) != PLIST_STRING)) { +		fprintf(stderr, "%s: Unable to get IMEI from lockdownd\n", __func__); +	} else { +		plist_dict_set_item(fields, "IMEI", plist_copy(node)); +	} +	if (node) { +		plist_free(node); +		node = NULL; +	} + +	// add MEID +	if ((lockdownd_get_value(lockdown, NULL, "MobileEquipmentIdentifier", &node) != LOCKDOWN_E_SUCCESS) || !node || (plist_get_node_type(node) != PLIST_STRING)) { +		fprintf(stderr, "%s: Unable to get MEID from lockdownd\n", __func__); +	} else { +		plist_dict_set_item(fields, "MEID", plist_copy(node)); +	} +	if (node) { +		plist_free(node); +		node = NULL; +	} + +	// add IMSI +	if ((lockdownd_get_value(lockdown, NULL, "InternationalMobileSubscriberIdentity", &node) != LOCKDOWN_E_SUCCESS) || !node || (plist_get_node_type(node) != PLIST_STRING)) { +		fprintf(stderr, "%s: Unable to get IMSI from lockdownd\n", __func__); +	} else { +		plist_dict_set_item(fields, "IMSI", plist_copy(node)); +	} +	if (node) { +		plist_free(node); +		node = NULL; +	} + +	// add ICCID +	if ((lockdownd_get_value(lockdown, NULL, "IntegratedCircuitCardIdentity", &node) != LOCKDOWN_E_SUCCESS) || !node || (plist_get_node_type(node) != PLIST_STRING)) { +		fprintf(stderr, "%s: Unable to get ICCID from lockdownd\n", __func__); +	} else { +		plist_dict_set_item(fields, "ICCID", plist_copy(node)); +	} +	if (node) { +		plist_free(node); +		node = NULL; +	} + +	// add activation-info +	if ((lockdownd_get_value(lockdown, NULL, "ActivationInfo", &node) != LOCKDOWN_E_SUCCESS) || !node || (plist_get_node_type(node) != PLIST_DICT)) { +		fprintf(stderr, "%s: Unable to get ActivationInfo from lockdownd\n", __func__); +		plist_free(fields); +		return IDEVICE_ACTIVATION_E_INCOMPLETE_INFO; +	} +	plist_dict_set_item(fields, "activation-info", plist_copy(node)); +	plist_free(node); +	node = NULL; + +	idevice_activation_request* tmp_request = (idevice_activation_request*) malloc(sizeof(idevice_activation_request)); + +	if (!tmp_request) { +		plist_free(fields); +		return IDEVICE_ACTIVATION_E_OUT_OF_MEMORY; +	} + +	tmp_request->client_type = client_type; +	tmp_request->content_type = IDEVICE_ACTIVATION_CONTENT_TYPE_MULTIPART_FORMDATA; +	tmp_request->url = strdup(IDEVICE_ACTIVATION_DEFAULT_URL); +	tmp_request->fields = fields; +	*request = tmp_request; + +	return IDEVICE_ACTIVATION_E_SUCCESS; +} + +void idevice_activation_request_free(idevice_activation_request_t request) +{ +	if (!request) +		return; + +	if (request->fields) +		plist_free(request->fields); + +	free(request); +} + +void idevice_activation_request_get_fields(idevice_activation_request_t request, plist_t* fields) +{ +	*fields = plist_copy(request->fields); +} + +void idevice_activation_request_set_fields(idevice_activation_request_t request, plist_t fields) +{ +	if (!fields) +		return; + +	if (request->content_type == IDEVICE_ACTIVATION_CONTENT_TYPE_URL_ENCODED) { +		// if at least one of the new fields has a different type than string, we have to change the type +		plist_dict_iter iter = NULL; +		plist_dict_new_iter(fields, &iter); +		plist_t item = NULL; +		do { +			plist_dict_next_item(fields, iter, NULL, &item); +			if (item && plist_get_node_type(item) != PLIST_STRING) { +				request->content_type = IDEVICE_ACTIVATION_CONTENT_TYPE_MULTIPART_FORMDATA; +				break; +			} +		} while(item); +	} + +	plist_dict_merge(&request->fields, fields); +} + +void idevice_activation_request_set_fields_from_response(idevice_activation_request_t request, const idevice_activation_response_t response) +{ +	plist_t response_fields = NULL; +	idevice_activation_response_get_fields(response, &response_fields); +	if (response_fields) { +		idevice_activation_request_set_fields(request, response_fields); +		free(response_fields); +	} +} + +void idevice_activation_request_set_field(idevice_activation_request_t request, const char* key, const char* value) +{ +	plist_dict_set_item(request->fields, key, plist_new_string(value)); +} + +void idevice_activation_request_get_field(idevice_activation_request_t request, const char* key, char** value) +{ +	char* tmp_value = NULL; + +	plist_t item = plist_dict_get_item(request->fields, key); + +	if (item && plist_get_node_type(item) == PLIST_STRING) { +		plist_get_string_val(item, &tmp_value); +	} else { +		uint32_t data_size = 0; +		plist_to_xml(item, &tmp_value, &data_size); +		plist_strip_xml(&tmp_value); +	} + +	*value = tmp_value; +} + +void idevice_activation_request_get_url(idevice_activation_request_t request, const char** url) +{ +	*url = request->url; +} + +void idevice_activation_request_set_url(idevice_activation_request_t request, const char* url) +{ +	if (request->url) { +		free(request->url); +	} + +	request->url = strdup(url); +} + +idevice_activation_error_t idevice_activation_response_new(idevice_activation_response_t* response) +{ +	idevice_activation_response_t tmp_response = (idevice_activation_response_t) malloc(sizeof(idevice_activation_response)); + +	if (!tmp_response) { +		return IDEVICE_ACTIVATION_E_OUT_OF_MEMORY; +	} + +	tmp_response->raw_content = NULL; +	tmp_response->raw_content_size = 0; +	tmp_response->content_type = IDEVICE_ACTIVATION_CONTENT_TYPE_UNKNOWN; +	tmp_response->title = NULL; +	tmp_response->description = NULL; +	tmp_response->activation_record = NULL; +	tmp_response->fields = plist_new_dict(); +	tmp_response->fields_require_input = plist_new_dict(); +	tmp_response->labels = plist_new_dict(); +	tmp_response->is_activation_ack = 0; +	tmp_response->is_auth_required = 0; +	tmp_response->has_errors = 0; +	*response = tmp_response; + +	return IDEVICE_ACTIVATION_E_SUCCESS; +} + +idevice_activation_error_t idevice_activation_response_new_from_html(const char* content, idevice_activation_response_t* response) +{ +	idevice_activation_response_t tmp_response = NULL; +	idevice_activation_error_t result = IDEVICE_ACTIVATION_E_SUCCESS; + +	result = idevice_activation_response_new(&tmp_response); +	if (result != IDEVICE_ACTIVATION_E_SUCCESS) { +		return result; +	} + +	const size_t tmp_size = strlen(content); +	char* tmp_content = (char*) malloc(sizeof(char) * tmp_size); + +	if (!tmp_content) { +		idevice_activation_response_free(tmp_response); +		return IDEVICE_ACTIVATION_E_OUT_OF_MEMORY; +	} + +	memcpy(tmp_content, content, tmp_size); + +	tmp_response->raw_content = tmp_content; +	tmp_response->raw_content_size = tmp_size; +	tmp_response->content_type = IDEVICE_ACTIVATION_CONTENT_TYPE_HTML; + +	result = idevice_activation_parse_html_response(tmp_response); +	if (result != IDEVICE_ACTIVATION_E_SUCCESS) { +		idevice_activation_response_free(tmp_response); +		return result; +	} + +	*response = tmp_response; + +	return result; +} + +idevice_activation_error_t idevice_activation_response_to_buffer(idevice_activation_response_t response, char** buffer, size_t* size) +{ +	char* tmp_buffer = (char*) malloc(sizeof(char) * response->raw_content_size); +	if (!tmp_buffer) { +		return IDEVICE_ACTIVATION_E_OUT_OF_MEMORY; +	} + +	memcpy(tmp_buffer, response->raw_content, response->raw_content_size); + +	*buffer = tmp_buffer; +	*size = response->raw_content_size; + +	return IDEVICE_ACTIVATION_E_SUCCESS; +} + +void idevice_activation_response_free(idevice_activation_response_t response) +{ +	if (response->raw_content) +		free(response->raw_content); +	if (response->title) +		free(response->title); +	if (response->description) +		free(response->description); +	if (response->activation_record) +		plist_free(response->activation_record); +	if (response->fields) +		plist_free(response->fields); +	if (response->fields_require_input) +		plist_free(response->fields_require_input); +	if (response->labels) +		plist_free(response->labels); +	free(response); +} + +void idevice_activation_response_get_field(idevice_activation_response_t response, const char* key, char** value) +{ +	*value = NULL; +	plist_t item = plist_dict_get_item(response->fields, key); + +	if (item && plist_get_node_type(item) == PLIST_STRING) { +		plist_get_string_val(item, value); +	} +} + +void idevice_activation_response_get_fields(idevice_activation_response_t response, plist_t* fields) +{ +	*fields = plist_copy(response->fields); +} + +void idevice_activation_response_get_label(idevice_activation_response_t response, const char* key, char** value) +{ +	*value = NULL; +	plist_t item = plist_dict_get_item(response->labels, key); +	if (item) { +		plist_get_string_val(item, value); +	} +} + +void idevice_activation_response_get_title(idevice_activation_response_t response, const char** title) +{ +	*title = response->title; +} + +void idevice_activation_response_get_description(idevice_activation_response_t response, const char** description) +{ +	*description = response->description; +} + +void idevice_activation_response_get_activation_record(idevice_activation_response_t response, plist_t* activation_record) +{ +	if (response->activation_record) { +		*activation_record = plist_copy(response->activation_record); +	} else { +		*activation_record = NULL; +	} +} + +int idevice_activation_response_is_activation_acknowledged(idevice_activation_response_t response) +{ +	return response->is_activation_ack; +} + +int idevice_activation_response_is_authentication_required(idevice_activation_response_t response) +{ +	return response->is_auth_required; +} + +int idevice_activation_response_field_requires_input(idevice_activation_response_t response, const char* key) +{ +	if (plist_dict_get_item(response->fields_require_input, key)) { +		return 1; +	} +	return 0; +} + +int idevice_activation_response_has_errors(idevice_activation_response_t response) +{ +	return response->has_errors; +} + +idevice_activation_error_t idevice_activation_send_request(idevice_activation_request_t request, idevice_activation_response_t* response) +{ +	idevice_activation_error_t result = IDEVICE_ACTIVATION_E_SUCCESS; + +	// check arguments +	if (!request) { +		return IDEVICE_ACTIVATION_E_INTERNAL_ERROR; +	} + +	plist_dict_iter iter = NULL; +	plist_dict_new_iter(request->fields, &iter); +	if (!iter) { +		return IDEVICE_ACTIVATION_E_INTERNAL_ERROR; +		goto cleanup; +	} + +	curl_global_init(CURL_GLOBAL_ALL); +	CURL* handle = curl_easy_init(); +	struct curl_httppost* form = NULL; + +	if (!handle) { +		result = IDEVICE_ACTIVATION_E_INTERNAL_ERROR; +		goto cleanup; +	} + +	switch (request->client_type) { +		case IDEVICE_ACTIVATION_CLIENT_MOBILE_ACTIVATION: +			curl_easy_setopt(handle, CURLOPT_USERAGENT, IDEVICE_ACTIVATION_USER_AGENT_IOS); +			break; +		case IDEVICE_ACTIVATION_CLIENT_ITUNES: +			curl_easy_setopt(handle, CURLOPT_USERAGENT, IDEVICE_ACTIVATION_USER_AGENT_ITUNES); +			break; +		default: +			result = IDEVICE_ACTIVATION_E_INTERNAL_ERROR; +			goto cleanup; +	} + +	char* key = NULL; +	char* svalue = NULL; +	plist_t value_node = NULL; + +	if (request->content_type == IDEVICE_ACTIVATION_CONTENT_TYPE_MULTIPART_FORMDATA) { +		struct curl_httppost* last = NULL; +		do { +			plist_dict_next_item(request->fields, iter, &key, &value_node); +			if (key != NULL) { +				if (value_node != NULL) { +					// serialize plist node as field value +					if (plist_get_node_type(value_node) == PLIST_STRING) { +						plist_get_string_val(value_node, &svalue); +					} else { +						uint32_t data_size = 0; +						plist_to_xml(value_node, &svalue, &data_size); +						plist_strip_xml(&svalue); +					} + +					curl_formadd(&form, &last, CURLFORM_COPYNAME, key, CURLFORM_COPYCONTENTS, svalue, CURLFORM_END); + +					if (svalue) +						free(svalue); +					svalue = NULL; +				} +			} +		} while(value_node != NULL); +		curl_easy_setopt(handle, CURLOPT_HTTPPOST, form); + +	} else if (request->content_type == IDEVICE_ACTIVATION_CONTENT_TYPE_URL_ENCODED) { +		char* postdata = (char*) malloc(sizeof(char)); +		postdata[0] = '\0'; +		do { +			plist_dict_next_item(request->fields, iter, &key, &value_node); +			if (key != NULL) { +				if (value_node != NULL) { +					// serialize plist node as field value +					if (plist_get_node_type(value_node) == PLIST_STRING) { +						plist_get_string_val(value_node, &svalue); +					} else { +						// only strings supported +						if (postdata) +							free(postdata); +						result = IDEVICE_ACTIVATION_E_UNSUPPORTED_FIELD_TYPE; +						goto cleanup; +					} + +					char* value_encoded = urlencode(svalue); +					if (value_encoded) { +						const size_t new_size = strlen(postdata) + strlen(key) + strlen(value_encoded) + 3; +						postdata = (char*) realloc(postdata, new_size); +						sprintf(&postdata[strlen(postdata)], "%s=%s&", key, value_encoded); +						free(value_encoded); +					} + +					if (svalue) +						free(svalue); +					svalue = NULL; +				} +			} +		} while(value_node != NULL); + +		// remove the last '&' +		const size_t postdata_len = strlen(postdata); +		if (postdata_len > 0) +			postdata[postdata_len - 1] = '\0'; + +		curl_easy_setopt(handle, CURLOPT_POST, 1); +		curl_easy_setopt(handle, CURLOPT_POSTFIELDS, postdata); +		curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE, strlen(postdata)); +	} +	else { +		result = IDEVICE_ACTIVATION_E_INTERNAL_ERROR; +		goto cleanup; +	} + +	idevice_activation_response_t tmp_response = NULL; +	result = idevice_activation_response_new(&tmp_response); +	if (result != IDEVICE_ACTIVATION_E_SUCCESS) { +		goto cleanup; +	} + +	curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0); +	curl_easy_setopt(handle, CURLOPT_WRITEDATA, tmp_response); +	curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, &idevice_activation_write_callback); +	curl_easy_setopt(handle, CURLOPT_HEADERDATA, tmp_response); +	curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, &idevice_activation_header_callback); +	curl_easy_setopt(handle, CURLOPT_URL, request->url); + +	// enable communication debugging +	if (debug_level > 0) { +		curl_easy_setopt(handle, CURLOPT_VERBOSE, 1); +	} + +	curl_easy_perform(handle); + +	if (debug_level > 0) { +		fprintf(stderr, "%*s\n", (int)tmp_response->raw_content_size, tmp_response->raw_content); +	} + +	result = idevice_activation_parse_raw_response(tmp_response); +	if (result != IDEVICE_ACTIVATION_E_SUCCESS) { +		goto cleanup; +	} + +	*response = tmp_response; + +	result = IDEVICE_ACTIVATION_E_SUCCESS; + +cleanup: +	if (iter) +		free(iter); +	if (form) +		curl_formfree(form); +	if (handle) +		curl_easy_cleanup(handle); + +	curl_global_cleanup(); + +	return result; +} | 
