wi_status wi_parse_plist(wi_t self, const char *from_buf, size_t length,
    plist_t *to_rpc_dict, bool *to_is_partial) {
  wi_private_t my = self->private_state;
  *to_is_partial = false;
  *to_rpc_dict = NULL;

  if (my->is_sim) {
    plist_from_bin(from_buf, length, to_rpc_dict);
  } else {
    plist_t wi_dict = NULL;
    plist_from_bin(from_buf, length, &wi_dict);
    if (!wi_dict) {
      return WI_ERROR;
    }
    plist_t wi_rpc = plist_dict_get_item(wi_dict, "WIRFinalMessageKey");
    if (!wi_rpc) {
      wi_rpc = plist_dict_get_item(wi_dict, "WIRPartialMessageKey");
      if (!wi_rpc) {
        return WI_ERROR;
      }
      *to_is_partial = true;
    }

    uint64_t rpc_len = 0;
    char *rpc_bin = NULL;
    plist_get_data_val(wi_rpc, &rpc_bin, &rpc_len);
    plist_free(wi_dict); // also frees wi_rpc
    if (!rpc_bin) {
      return WI_ERROR;
    }
    // assert rpc_len < MAX_RPC_LEN?

    size_t p_length = my->partial->tail - my->partial->head;
    if (*to_is_partial || p_length) {
      if (cb_ensure_capacity(my->partial, rpc_len)) {
        return self->on_error(self, "Out of memory");
      }
      memcpy(my->partial->tail, rpc_bin, rpc_len);
      my->partial->tail += rpc_len;
      p_length += rpc_len;
      free(rpc_bin);
      if (*to_is_partial) {
        return WI_SUCCESS;
      }
    }

    if (p_length) {
      plist_from_bin(my->partial->head, (uint32_t)p_length, to_rpc_dict);
      cb_clear(my->partial);
    } else {
      plist_from_bin(rpc_bin, (uint32_t)rpc_len, to_rpc_dict);
      free(rpc_bin);
    }
  }

  return (*to_rpc_dict ? WI_SUCCESS : WI_ERROR);
}
Exemple #2
0
  //
  // If plist_from_bin will fail, xmlBuff_ will remain unchanged, pointing to PREVIOUS-SAVED xml buffer.
  //
  const CharVt& PlistEntry::GetXML(CharVt&& BplistBuff)
  {
    GuardedPlist plist;
    try
    {
      plist_from_bin(BplistBuff.data(), BplistBuff.size(), plist.get_ptr());
    }
    catch (...)
    {
      // catch SEH exceptions (/withSEH)
      throw std::system_error(std::error_code(EFAULT, std::generic_category()), "Invalid bplist file! Cant parse it");
    }

    uint32_t cbXML = 0;
    char* pXML_{};
    plist_to_xml( plist, &pXML_, &cbXML );

    if ( cbXML > 0 )
    {
      xmlBuff_.assign( pXML_, pXML_ + cbXML );
      // free mem allocated by plist_to_xml
      free( pXML_ );
    }

    if ( xmlBuff_.empty() )
    {
      contentType_ = ContentType::corrupted;
      throw std::runtime_error( "error converting bplist to xml" );
    }

    contentType_ = ContentType::xml;

    return xmlBuff_;
  }
Exemple #3
0
/** Polls the iPhone for MobileSync data.
 *
 * @param client The MobileSync client
 * @param plist A pointer to the location where the plist should be stored
 *
 * @return an error code
 */
mobilesync_error_t mobilesync_recv(mobilesync_client_t client, plist_t * plist)
{
	if (!client || !plist || (plist && *plist))
		return MOBILESYNC_E_INVALID_ARG;
	mobilesync_error_t ret = MOBILESYNC_E_UNKNOWN_ERROR;
	char *receive = NULL;
	uint32_t datalen = 0, bytes = 0, received_bytes = 0;

	ret = iphone_device_recv(client->connection, (char *) &datalen, sizeof(datalen), &bytes);
	datalen = ntohl(datalen);

	receive = (char *) malloc(sizeof(char) * datalen);

	/* fill buffer and request more packets if needed */
	while ((received_bytes < datalen) && (ret == MOBILESYNC_E_SUCCESS)) {
		ret = iphone_device_recv(client->connection, receive + received_bytes, datalen - received_bytes, &bytes);
		received_bytes += bytes;
	}

	if (ret != MOBILESYNC_E_SUCCESS) {
		free(receive);
		return MOBILESYNC_E_MUX_ERROR;
	}

	plist_from_bin(receive, received_bytes, plist);
	free(receive);

	char *XMLContent = NULL;
	uint32_t length = 0;
	plist_to_xml(*plist, &XMLContent, &length);
	log_dbg_msg(DBGMASK_MOBILESYNC, "%s: plist size: %i\nbuffer :\n%s\n", __func__, length, XMLContent);
	free(XMLContent);

	return ret;
}
/**
 * Receives a plist using the given property list service client.
 * Internally used generic plist receive function.
 *
 * @param client The property list service client to use for receiving
 * @param plist pointer to a plist_t that will point to the received plist
 *      upon successful return
 * @param timeout Maximum time in milliseconds to wait for data.
 *
 * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success,
 *      PROPERTY_LIST_SERVICE_E_INVALID_ARG when client or *plist is NULL,
 *      PROPERTY_LIST_SERVICE_E_PLIST_ERROR when the received data cannot be
 *      converted to a plist, PROPERTY_LIST_SERVICE_E_MUX_ERROR when a
 *      communication error occurs, or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR
 *      when an unspecified error occurs.
 */
static property_list_service_error_t internal_plist_receive_timeout(property_list_service_client_t client, plist_t *plist, unsigned int timeout)
{
	property_list_service_error_t res = PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR;
	uint32_t pktlen = 0;
	uint32_t bytes = 0;

	if (!client || (client && !client->connection) || !plist) {
		return PROPERTY_LIST_SERVICE_E_INVALID_ARG;
	}

	idevice_connection_receive_timeout(client->connection, (char*)&pktlen, sizeof(pktlen), &bytes, timeout);
	debug_info("initial read=%i", bytes);
	if (bytes < 4) {
		debug_info("initial read failed!");
		return PROPERTY_LIST_SERVICE_E_MUX_ERROR;
	} else {
		pktlen = be32toh(pktlen);
		if (pktlen < (1 << 24)) { /* prevent huge buffers */
			uint32_t curlen = 0;
			char *content = NULL;
			debug_info("%d bytes following", pktlen);
			content = (char*)malloc(pktlen);

			while (curlen < pktlen) {
				idevice_connection_receive(client->connection, content+curlen, pktlen-curlen, &bytes);
				if (bytes <= 0) {
					res = PROPERTY_LIST_SERVICE_E_MUX_ERROR;
					break;
				}
				debug_info("received %d bytes", bytes);
				curlen += bytes;
			}
			if (!memcmp(content, "bplist00", 8)) {
				plist_from_bin(content, pktlen, plist);
			} else {
				/* iOS 4.3+ hack: plist data might contain invalid characters, thus we convert those to spaces */
				for (bytes = 0; bytes < pktlen-1; bytes++) {
					if ((content[bytes] >= 0) && (content[bytes] < 0x20) && (content[bytes] != 0x09) && (content[bytes] != 0x0a) && (content[bytes] != 0x0d))
						content[bytes] = 0x20;
				}
				plist_from_xml(content, pktlen, plist);
			}
			if (*plist) {
				debug_plist(*plist);
				res = PROPERTY_LIST_SERVICE_E_SUCCESS;
			} else {
				res = PROPERTY_LIST_SERVICE_E_PLIST_ERROR;
			}
			free(content);
			content = NULL;
		} else {
			res = PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR;
		}
	}
	return res;
}
Exemple #5
0
PLIST_API void plist_from_memory(const char *plist_data, uint32_t length, plist_t * plist)
{
    if (length < 8) {
        *plist = NULL;
        return;
    }

    if (plist_is_binary(plist_data, length)) {
        plist_from_bin(plist_data, length, plist);
    } else {
        plist_from_xml(plist_data, length, plist);
    }
}
Exemple #6
0
static int rproxy_recv_dict(idevice_connection_t connection, plist_t * dict, unsigned int timeout)
{
    int res = -1;
    char * content = NULL;
    uint32_t dict_length = 0;

    /* Read the size of the plist buffer */
    if (IDEVICE_E_SUCCESS != idevice_connection_receive_all(connection, (char *)&dict_length, sizeof(dict_length), timeout)) {
        error("ERROR: Failed to receive plist size\n");
        goto cleanup;
    }

    /* Allocate a buffer for the plist */
    content = (char*)malloc(dict_length);
    if (NULL == content) {
        error("ERROR: Out of memory\n");
        goto cleanup;
    }

    /* Read the plist */
    if (IDEVICE_E_SUCCESS != idevice_connection_receive_all(connection, content, dict_length, timeout)) {
        error("ERROR: Failed to receive plist data\n");
        goto cleanup;
    }

    /* Finally, parse the plist */
    plist_from_bin(content, dict_length, dict);
    if (*dict) {
        res = 0;
    }
    else {
        error("ERROR: Failed to parse the recieved plist\n");
    }

cleanup:
    if (content) {
        free(content);
    }
    return res;
}
Exemple #7
0
int plist_read_from_filename(plist_t *plist, const char *filename)
{
	char *buffer = NULL;
	uint64_t length;

	if (!filename)
		return 0;

	buffer_read_from_filename(filename, &buffer, &length);

	if (!buffer) {
		return 0;
	}

	if ((length > 8) && (memcmp(buffer, "bplist00", 8) == 0)) {
		plist_from_bin(buffer, length, plist);
	} else {
		plist_from_xml(buffer, length, plist);
	}

	free(buffer);

	return 1;
}
Exemple #8
0
int main(int argc, char *argv[])
{
    FILE *iplist1 = NULL;
    FILE *iplist2 = NULL;
    plist_t root_node1 = NULL;
    plist_t root_node2 = NULL;
    char *plist_1 = NULL;
    char *plist_2 = NULL;
    int size_in1 = 0;
    int size_in2 = 0;
    char *file_in1 = NULL;
    char *file_in2 = NULL;
    int res = 0;

    struct stat *filestats1 = (struct stat *) malloc(sizeof(struct stat));
    struct stat *filestats2 = (struct stat *) malloc(sizeof(struct stat));

    if (argc!= 3)
    {
        printf("Wrong input\n");
        return 1;
    }

    file_in1 = argv[1];
    file_in2 = argv[2];

    //read input file
    iplist1 = fopen(file_in1, "rb");
    iplist2 = fopen(file_in2, "rb");

    if (!iplist1 || !iplist2)
    {
        printf("File does not exists\n");
        return 2;
    }

    stat(file_in1, filestats1);
    stat(file_in2, filestats2);

    size_in1 = filestats1->st_size;
    size_in2 = filestats2->st_size;

    plist_1 = (char *) malloc(sizeof(char) * (size_in1 + 1));
    plist_2 = (char *) malloc(sizeof(char) * (size_in2 + 1));

    fread(plist_1, sizeof(char), size_in1, iplist1);
    fread(plist_2, sizeof(char), size_in2, iplist2);

    fclose(iplist1);
    fclose(iplist2);

    if (memcmp(plist_1, "bplist00", 8) == 0)
        plist_from_bin(plist_1, size_in1, &root_node1);
    else
        plist_from_xml(plist_1, size_in1, &root_node1);

    if (memcmp(plist_2, "bplist00", 8) == 0)
        plist_from_bin(plist_2, size_in2, &root_node2);
    else
        plist_from_xml(plist_2, size_in2, &root_node2);

    if (!root_node1 || !root_node2)
    {
        printf("PList parsing failed\n");
        return 3;
    }
    else
        printf("PList parsing succeeded\n");

    res = compare_plist(root_node1, root_node2);


    plist_free(root_node1);
    plist_free(root_node2);

    free(plist_1);
    free(plist_2);
    free(filestats1);
    free(filestats2);

    return !res;
}
/**
 * Receives a plist using the given property list service client.
 * Internally used generic plist receive function.
 *
 * @param client The property list service client to use for receiving
 * @param plist pointer to a plist_t that will point to the received plist
 *      upon successful return
 * @param timeout Maximum time in milliseconds to wait for data.
 *
 * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success,
 *      PROPERTY_LIST_SERVICE_E_INVALID_ARG when client or *plist is NULL,
 *      PROPERTY_LIST_SERVICE_E_PLIST_ERROR when the received data cannot be
 *      converted to a plist, PROPERTY_LIST_SERVICE_E_MUX_ERROR when a
 *      communication error occurs, or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR
 *      when an unspecified error occurs.
 */
static property_list_service_error_t internal_plist_receive_timeout(property_list_service_client_t client, plist_t *plist, unsigned int timeout)
{
	property_list_service_error_t res = PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR;
	uint32_t pktlen = 0;
	uint32_t bytes = 0;

	if (!client || (client && !client->parent) || !plist) {
		return PROPERTY_LIST_SERVICE_E_INVALID_ARG;
	}

	*plist = NULL;
	service_error_t serr = service_receive_with_timeout(client->parent, (char*)&pktlen, sizeof(pktlen), &bytes, timeout);
	if ((serr == SERVICE_E_SUCCESS) && (bytes == 0)) {
		return PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT;
	}
	debug_info("initial read=%i", bytes);
	if (bytes < 4) {
		debug_info("initial read failed!");
		return PROPERTY_LIST_SERVICE_E_MUX_ERROR;
	} else {
		uint32_t curlen = 0;
		char *content = NULL;

		pktlen = be32toh(pktlen);
		debug_info("%d bytes following", pktlen);
		content = (char*)malloc(pktlen);
		if (!content) {
			debug_info("out of memory when allocating %d bytes", pktlen);
			return PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR;
		}

		while (curlen < pktlen) {
			service_receive(client->parent, content+curlen, pktlen-curlen, &bytes);
			if (bytes <= 0) {
				res = PROPERTY_LIST_SERVICE_E_MUX_ERROR;
				break;
			}
			debug_info("received %d bytes", bytes);
			curlen += bytes;
		}
		if (curlen < pktlen) {
			debug_info("received incomplete packet (%d of %d bytes)", curlen, pktlen);
			if (curlen > 0) {
				debug_info("incomplete packet following:");
				debug_buffer(content, curlen);
			}
			free(content);
			return res;
		}
		if ((pktlen > 8) && !memcmp(content, "bplist00", 8)) {
			plist_from_bin(content, pktlen, plist);
		} else if ((pktlen > 5) && !memcmp(content, "<?xml", 5)) {
			/* iOS 4.3+ hack: plist data might contain invalid characters, thus we convert those to spaces */
			for (bytes = 0; bytes < pktlen-1; bytes++) {
				if ((content[bytes] >= 0) && (content[bytes] < 0x20) && (content[bytes] != 0x09) && (content[bytes] != 0x0a) && (content[bytes] != 0x0d))
					content[bytes] = 0x20;
			}
			plist_from_xml(content, pktlen, plist);
		} else {
			debug_info("WARNING: received unexpected non-plist content");
			debug_buffer(content, pktlen);
		}
		if (*plist) {
			debug_plist(*plist);
			res = PROPERTY_LIST_SERVICE_E_SUCCESS;
		} else {
			res = PROPERTY_LIST_SERVICE_E_PLIST_ERROR;
		}
		free(content);
		content = NULL;
	}
	return res;
}
Exemple #10
0
int main(int argc, char *argv[])
{
    FILE *iplist = NULL;
    plist_t root_node1 = NULL;
    plist_t root_node2 = NULL;
    char *plist_xml = NULL;
    char *plist_xml2 = NULL;
    char *plist_bin = NULL;
    int size_in = 0;
    uint32_t size_out = 0;
    uint32_t size_out2 = 0;
    char *file_in = NULL;
    char *file_out = NULL;
    struct stat *filestats = (struct stat *) malloc(sizeof(struct stat));
    if (argc != 3)
    {
        printf("Wrong input\n");
        return 1;
    }

    file_in = argv[1];
    file_out = argv[2];
    //read input file
    iplist = fopen(file_in, "rb");

    if (!iplist)
    {
        printf("File does not exists\n");
        return 2;
    }
    printf("File %s is open\n", file_in);
    stat(file_in, filestats);
    size_in = filestats->st_size;
    plist_xml = (char *) malloc(sizeof(char) * (size_in + 1));
    fread(plist_xml, sizeof(char), size_in, iplist);
    fclose(iplist);


    //convert one format to another
    plist_from_xml(plist_xml, size_in, &root_node1);
    if (!root_node1)
    {
        printf("PList XML parsing failed\n");
        return 3;
    }
    else
        printf("PList XML parsing succeeded\n");

    plist_to_bin(root_node1, &plist_bin, &size_out);
    if (!plist_bin)
    {
        printf("PList BIN writing failed\n");
        return 4;
    }
    else
        printf("PList BIN writing succeeded\n");

    plist_from_bin(plist_bin, size_out, &root_node2);
    if (!root_node2)
    {
        printf("PList BIN parsing failed\n");
        return 5;
    }
    else
        printf("PList BIN parsing succeeded\n");

    plist_to_xml(root_node2, &plist_xml2, &size_out2);
    if (!plist_xml2)
    {
        printf("PList XML writing failed\n");
        return 8;
    }
    else
        printf("PList XML writing succeeded\n");

    if (plist_xml2)
    {
        FILE *oplist = NULL;
        oplist = fopen(file_out, "wb");
        fwrite(plist_xml2, size_out2, sizeof(char), oplist);
        fclose(oplist);
    }

    plist_free(root_node1);
    plist_free(root_node2);
    free(plist_bin);
    free(plist_xml);
    free(plist_xml2);
    free(filestats);

    if ((uint32_t)size_in != size_out2)
    {
        printf("Size of input and output is different\n");
        printf("Input size : %i\n", size_in);
        printf("Output size : %i\n", size_out2);
    }

    //success
    return 0;
}
webinspector_error_t webinspector_receive_with_timeout(webinspector_client_t client, plist_t * plist, uint32_t timeout_ms)
{
	webinspector_error_t res = WEBINSPECTOR_E_UNKNOWN_ERROR;
	plist_t message = NULL;
	plist_t key = NULL;

	int is_final_message = 1;

	char* buffer = NULL;
	uint64_t length = 0;

	char* packet = NULL;
	char* newpacket = NULL;
	uint64_t packet_length = 0;

	debug_info("Receiving webinspector message...");

	do {
		/* receive message */
		res = webinspector_error(property_list_service_receive_plist_with_timeout(client->parent, &message, timeout_ms));
		if (res != WEBINSPECTOR_E_SUCCESS || !message) {
			debug_info("Could not receive message, error %d", res);
			plist_free(message);
			return WEBINSPECTOR_E_MUX_ERROR;
		}

		/* get message key */
		key = plist_dict_get_item(message, "WIRFinalMessageKey");
		if (!key) {
			key = plist_dict_get_item(message, "WIRPartialMessageKey");
			if (!key) {
				debug_info("ERROR: Unable to read message key.");
				plist_free(message);
				return WEBINSPECTOR_E_PLIST_ERROR;
			}
			is_final_message = 0;
		} else {
			is_final_message = 1;
		}

		/* read partial data */
		plist_get_data_val(key, &buffer, &length);
		if (!buffer || length == 0 || length > 0xFFFFFFFF) {
			debug_info("ERROR: Unable to get the inner plist binary data.");
			free(packet);
			free(buffer);
			return WEBINSPECTOR_E_PLIST_ERROR;
		}

		/* (re)allocate packet data */
		if (!packet) {
			packet = (char*)malloc(length * sizeof(char));
		} else {
			newpacket = (char*)realloc(packet, (packet_length + length) * sizeof(char));
			packet = newpacket;
		}

		/* copy partial data into final packet data */
		memcpy(packet + packet_length, buffer, length);

		/* cleanup buffer */
		free(buffer);
		buffer = NULL;

		if (message) {
			plist_free(message);
			message = NULL;
		}

		/* adjust packet length */
		packet_length += length;
		length = 0;
	} while(!is_final_message);

	/* read final message */
	if (packet_length) {
		plist_from_bin(packet, (uint32_t)packet_length, plist);
		if (!*plist) {
			debug_info("Error restoring the final plist.");
			free(packet);
			return WEBINSPECTOR_E_PLIST_ERROR;
		}

		debug_plist(*plist);
	}

	if (packet) {
		free(packet);
	}

	return res;
}
int get_shsh_blobs(struct idevicerestore_client_t* client, uint64_t ecid, unsigned char* nonce, int nonce_size, plist_t build_identity, plist_t* tss) {
	plist_t request = NULL;
	plist_t response = NULL;
	*tss = NULL;

	if ((client->build_major <= 8) || (client->flags & FLAG_CUSTOM)) {
		error("checking for local shsh\n");

		/* first check for local copy */
		char zfn[1024];
		if (client->version) {
			if (client->cache_dir) {
				sprintf(zfn, "%s/shsh/" FMT_qu "-%s-%s.shsh", client->cache_dir, (long long int)client->ecid, client->device->product_type, client->version);
			} else {
				sprintf(zfn, "shsh/" FMT_qu "-%s-%s.shsh", (long long int)client->ecid, client->device->product_type, client->version);
			}
			struct stat fst;
			if (stat(zfn, &fst) == 0) {
				gzFile zf = gzopen(zfn, "rb");
				if (zf) {
					int blen = 0;
					int readsize = 16384;
					int bufsize = readsize;
					char* bin = (char*)malloc(bufsize);
					char* p = bin;
					do {
						int bytes_read = gzread(zf, p, readsize);
						if (bytes_read < 0) {
							fprintf(stderr, "Error reading gz compressed data\n");
							exit(EXIT_FAILURE);
						}
						blen += bytes_read;
						if (bytes_read < readsize) {
							if (gzeof(zf)) {
								bufsize += bytes_read;
								break;
							}
						}
						bufsize += readsize;
						bin = realloc(bin, bufsize);
						p = bin + blen;
					} while (!gzeof(zf));
					gzclose(zf);
					if (blen > 0) {
						if (memcmp(bin, "bplist00", 8) == 0) {
							plist_from_bin(bin, blen, tss);
						} else {
							plist_from_xml(bin, blen, tss);
						}
					}
					free(bin);
				}
			} else {
				error("no local file %s\n", zfn);
			}
		} else {
			error("No version found?!\n");
		}
	}

	if (*tss) {
		info("Using cached SHSH\n");
		return 0;
	} else {
		info("Trying to fetch new SHSH blob\n");
	}

	request = tss_create_request(build_identity, ecid, nonce, nonce_size);
	if (request == NULL) {
		error("ERROR: Unable to create TSS request\n");
		return -1;
	}

	response = tss_send_request(request, client->tss_url);
	if (response == NULL) {
		info("ERROR: Unable to send TSS request\n");
		plist_free(request);
		return -1;
	}

	info("Received SHSH blobs\n");

	plist_free(request);
	*tss = response;
	return 0;
}
int get_shsh_blobs(struct idevicerestore_client_t* client, uint64_t ecid, unsigned char* nonce, int nonce_size, plist_t build_identity, plist_t* tss) {
	plist_t request = NULL;
	plist_t response = NULL;
	*tss = NULL;

	if ((client->build[0] <= '8') || (client->flags & FLAG_CUSTOM)) {
		error("checking for local shsh\n");

		/* first check for local copy */
		char zfn[512];
		if (client->version) {
			sprintf(zfn, "shsh/" FMT_qu "-%s-%s.shsh", (long long int)client->ecid, client->device->product, client->version);
			struct stat fst;
			if (stat(zfn, &fst) == 0) {
				gzFile zf = gzopen(zfn, "rb");
				if (zf) {
					unsigned char bin[65536];
					int blen = gzread(zf, bin, sizeof(bin));
					if (blen > 0) {
						if (memcmp(bin, "bplist00", 8) == 0) {
							plist_from_bin(bin, blen, tss);
						} else {
							plist_from_xml(bin, blen, tss);
						}
					}
					gzclose(zf);
				}
			} else {
				error("no local file %s\n", zfn);
			}
		} else {
			error("No version found?!\n");
		}
	}

	if (*tss) {
		info("Using cached SHSH\n");
		return 0;
	} else {
		info("Trying to fetch new SHSH blob\n");
	}

	request = tss_create_request(build_identity, ecid, nonce, nonce_size);
	if (request == NULL) {
		error("ERROR: Unable to create TSS request\n");
		return -1;
	}

	info("Sending TSS request... ");
	response = tss_send_request(request);
	if (response == NULL) {
		info("ERROR: Unable to send TSS request\n");
		plist_free(request);
		return -1;
	}

	info("received SHSH blobs\n");

	plist_free(request);
	*tss = response;
	return 0;
}
int main(int argc, char **argv)
{
	idevice_t phone = NULL;
	lockdownd_client_t client = NULL;
	instproxy_client_t ipc = NULL;
	instproxy_error_t err;
	np_client_t np = NULL;
	afc_client_t afc = NULL;
#ifdef HAVE_LIBIMOBILEDEVICE_1_1_5
	lockdownd_service_descriptor_t service = NULL;
#else
	uint16_t service = 0;
#endif
	int res = 0;
	char *bundleidentifier = NULL;

	parse_opts(argc, argv);

	argc -= optind;
	argv += optind;

	if (IDEVICE_E_SUCCESS != idevice_new(&phone, udid)) {
		fprintf(stderr, "No iOS device found, is it plugged in?\n");
		return -1;
	}

	if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "ideviceinstaller")) {
		fprintf(stderr, "Could not connect to lockdownd. Exiting.\n");
		goto leave_cleanup;
	}

	if ((lockdownd_start_service
		 (client, "com.apple.mobile.notification_proxy",
		  &service) != LOCKDOWN_E_SUCCESS) || !service) {
		fprintf(stderr,
				"Could not start com.apple.mobile.notification_proxy!\n");
		goto leave_cleanup;
	}

	np_error_t nperr = np_client_new(phone, service, &np);
#ifdef HAVE_LIBIMOBILEDEVICE_1_1_5
	if (service) {
		lockdownd_service_descriptor_free(service);
	}
	service = NULL;
#else
	service = 0;
#endif
	if (nperr != NP_E_SUCCESS) {
		fprintf(stderr, "Could not connect to notification_proxy!\n");
		goto leave_cleanup;
	}

#ifdef HAVE_LIBIMOBILEDEVICE_1_1
	np_set_notify_callback(np, notifier, NULL);
#else
	np_set_notify_callback(np, notifier);
#endif

	const char *noties[3] = { NP_APP_INSTALLED, NP_APP_UNINSTALLED, NULL };

	np_observe_notifications(np, noties);

run_again:
#ifdef HAVE_LIBIMOBILEDEVICE_1_1_5
	if (service) {
		lockdownd_service_descriptor_free(service);
	}
	service = NULL;
#else
	service = 0;
#endif
	if ((lockdownd_start_service(client, "com.apple.mobile.installation_proxy",
		  &service) != LOCKDOWN_E_SUCCESS) || !service) {
		fprintf(stderr,
				"Could not start com.apple.mobile.installation_proxy!\n");
		goto leave_cleanup;
	}

	err = instproxy_client_new(phone, service, &ipc);
#ifdef HAVE_LIBIMOBILEDEVICE_1_1_5
	if (service) {
		lockdownd_service_descriptor_free(service);
	}
	service = NULL;
#else
	service = 0;
#endif
	if (err != INSTPROXY_E_SUCCESS) {
		fprintf(stderr, "Could not connect to installation_proxy!\n");
		goto leave_cleanup;
	}

	setbuf(stdout, NULL);

	if (last_status) {
		free(last_status);
		last_status = NULL;
	}
	notification_expected = 0;

	if (cmd == CMD_LIST_APPS) {
		int xml_mode = 0;
		plist_t client_opts = instproxy_client_options_new();
		instproxy_client_options_add(client_opts, "ApplicationType", "User", NULL);
		plist_t apps = NULL;

		/* look for options */
		if (options) {
			char *opts = strdup(options);
			char *elem = strtok(opts, ",");
			while (elem) {
				if (!strcmp(elem, "list_system")) {
					if (!client_opts) {
						client_opts = instproxy_client_options_new();
					}
					instproxy_client_options_add(client_opts, "ApplicationType", "System", NULL);
				} else if (!strcmp(elem, "list_all")) {
					instproxy_client_options_free(client_opts);
					client_opts = NULL;
				} else if (!strcmp(elem, "list_user")) {
					/* do nothing, we're already set */
				} else if (!strcmp(elem, "xml")) {
					xml_mode = 1;
				}
				elem = strtok(NULL, ",");
			}
			free(opts);
		}

		err = instproxy_browse(ipc, client_opts, &apps);
		instproxy_client_options_free(client_opts);
		if (err != INSTPROXY_E_SUCCESS) {
			fprintf(stderr, "ERROR: instproxy_browse returned %d\n", err);
			goto leave_cleanup;
		}
		if (!apps || (plist_get_node_type(apps) != PLIST_ARRAY)) {
			fprintf(stderr,
					"ERROR: instproxy_browse returnd an invalid plist!\n");
			goto leave_cleanup;
		}
		if (xml_mode) {
			char *xml = NULL;
			uint32_t len = 0;

			plist_to_xml(apps, &xml, &len);
			if (xml) {
				puts(xml);
				free(xml);
			}
			plist_free(apps);
			goto leave_cleanup;
		}
		printf("Total: %d apps\n", plist_array_get_size(apps));
		uint32_t i = 0;
		for (i = 0; i < plist_array_get_size(apps); i++) {
			plist_t app = plist_array_get_item(apps, i);
			plist_t p_appid =
				plist_dict_get_item(app, "CFBundleIdentifier");
			char *s_appid = NULL;
			char *s_dispName = NULL;
			char *s_version = NULL;
			plist_t dispName =
				plist_dict_get_item(app, "CFBundleDisplayName");
			plist_t version = plist_dict_get_item(app, "CFBundleVersion");

			if (p_appid) {
				plist_get_string_val(p_appid, &s_appid);
			}
			if (!s_appid) {
				fprintf(stderr, "ERROR: Failed to get APPID!\n");
				break;
			}

			if (dispName) {
				plist_get_string_val(dispName, &s_dispName);
			}
			if (version) {
				plist_get_string_val(version, &s_version);
			}

			if (!s_dispName) {
				s_dispName = strdup(s_appid);
			}
			if (s_version) {
				printf("%s - %s %s\n", s_appid, s_dispName, s_version);
				free(s_version);
			} else {
				printf("%s - %s\n", s_appid, s_dispName);
			}
			free(s_dispName);
			free(s_appid);
		}
		plist_free(apps);
	} else if (cmd == CMD_INSTALL || cmd == CMD_UPGRADE) {
		plist_t sinf = NULL;
		plist_t meta = NULL;
		char *pkgname = NULL;
		struct stat fst;
		uint64_t af = 0;
		char buf[8192];

#ifdef HAVE_LIBIMOBILEDEVICE_1_1_5
		if (service) {
			lockdownd_service_descriptor_free(service);
		}
		service = NULL;
#else
		service = 0;
#endif
		if ((lockdownd_start_service(client, "com.apple.afc", &service) !=
			 LOCKDOWN_E_SUCCESS) || !service) {
			fprintf(stderr, "Could not start com.apple.afc!\n");
			goto leave_cleanup;
		}

		lockdownd_client_free(client);
		client = NULL;

		if (afc_client_new(phone, service, &afc) != INSTPROXY_E_SUCCESS) {
			fprintf(stderr, "Could not connect to AFC!\n");
			goto leave_cleanup;
		}

		if (stat(appid, &fst) != 0) {
			fprintf(stderr, "ERROR: stat: %s: %s\n", appid, strerror(errno));
			goto leave_cleanup;
		}

		char **strs = NULL;
		if (afc_get_file_info(afc, PKG_PATH, &strs) != AFC_E_SUCCESS) {
			if (afc_make_directory(afc, PKG_PATH) != AFC_E_SUCCESS) {
				fprintf(stderr, "WARNING: Could not create directory '%s' on device!\n", PKG_PATH);
			}
		}
		if (strs) {
			int i = 0;
			while (strs[i]) {
				free(strs[i]);
				i++;
			}
			free(strs);
		}

		plist_t client_opts = instproxy_client_options_new();

		/* open install package */
		int errp = 0;
		struct zip *zf = NULL;
		
		if ((strlen(appid) > 5) && (strcmp(&appid[strlen(appid)-5], ".ipcc") == 0)) {
			zf = zip_open(appid, 0, &errp);
			if (!zf) {
				fprintf(stderr, "ERROR: zip_open: %s: %d\n", appid, errp);
				goto leave_cleanup;
			}

			char* ipcc = strdup(appid);
			if ((asprintf(&pkgname, "%s/%s", PKG_PATH, basename(ipcc)) > 0) && pkgname) {
				afc_make_directory(afc, pkgname);
			}

			printf("Uploading %s package contents... ", basename(ipcc));

			/* extract the contents of the .ipcc file to PublicStaging/<name>.ipcc directory */
			zip_uint64_t numzf = zip_get_num_entries(zf, 0);
			zip_uint64_t i = 0;
			for (i = 0; numzf > 0 && i < numzf; i++) {
				const char* zname = zip_get_name(zf, i, 0);
				char* dstpath = NULL;
				if (!zname) continue;
				if (zname[strlen(zname)-1] == '/') {
					// directory
					if ((asprintf(&dstpath, "%s/%s/%s", PKG_PATH, basename(ipcc), zname) > 0) && dstpath) {
						afc_make_directory(afc, dstpath);						}
					free(dstpath);
					dstpath = NULL;
				} else {
					// file
					struct zip_file* zfile = zip_fopen_index(zf, i, 0);
					if (!zfile) continue;

					if ((asprintf(&dstpath, "%s/%s/%s", PKG_PATH, basename(ipcc), zname) <= 0) || !dstpath || (afc_file_open(afc, dstpath, AFC_FOPEN_WRONLY, &af) != AFC_E_SUCCESS)) {
						fprintf(stderr, "ERROR: can't open afc://%s for writing\n", dstpath);
						free(dstpath);
						dstpath = NULL;
						zip_fclose(zfile);
						continue;
					}

					struct zip_stat zs;
					zip_stat_init(&zs);
					if (zip_stat_index(zf, i, 0, &zs) != 0) {
						fprintf(stderr, "ERROR: zip_stat_index %" PRIu64 " failed!\n", i);
						free(dstpath);
						dstpath = NULL;
						zip_fclose(zfile);
						continue;
					}

					free(dstpath);
					dstpath = NULL;

					zip_uint64_t zfsize = 0;
					while (zfsize < zs.size) {
						zip_int64_t amount = zip_fread(zfile, buf, sizeof(buf));
						if (amount == 0) {
							break;
						}

						if (amount > 0) {
							uint32_t written, total = 0;
							while (total < amount) {
								written = 0;
								if (afc_file_write(afc, af, buf, amount, &written) !=
									AFC_E_SUCCESS) {
									fprintf(stderr, "AFC Write error!\n");
									break;
								}
								total += written;
							}
							if (total != amount) {
								fprintf(stderr, "Error: wrote only %d of %" PRIi64 "\n", total, amount);
								afc_file_close(afc, af);
								zip_fclose(zfile);
								free(dstpath);
								goto leave_cleanup;
							}
						}

						zfsize += amount;
					}

					afc_file_close(afc, af);
					af = 0;

					zip_fclose(zfile);
				}
			}
			free(ipcc);
			printf("DONE.\n");

			instproxy_client_options_add(client_opts, "PackageType", "CarrierBundle", NULL);
		} else if (S_ISDIR(fst.st_mode)) {
			/* upload developer app directory */
			instproxy_client_options_add(client_opts, "PackageType", "Developer", NULL);

			if (asprintf(&pkgname, "%s/%s", PKG_PATH, basename(appid)) < 0) {
				fprintf(stderr, "ERROR: Out of memory allocating pkgname!?\n");
				goto leave_cleanup;
			}

			printf("Uploading %s package contents... ", basename(appid));
			afc_upload_dir(afc, appid, pkgname);
			printf("DONE.\n");
		} else {
			zf = zip_open(appid, 0, &errp);
			if (!zf) {
				fprintf(stderr, "ERROR: zip_open: %s: %d\n", appid, errp);
				goto leave_cleanup;
			}

			/* extract iTunesMetadata.plist from package */
			char *zbuf = NULL;
			uint32_t len = 0;
			plist_t meta_dict = NULL;
			if (zip_get_contents(zf, ITUNES_METADATA_PLIST_FILENAME, 0, &zbuf, &len) == 0) {
				meta = plist_new_data(zbuf, len);
				if (memcmp(zbuf, "bplist00", 8) == 0) {
					plist_from_bin(zbuf, len, &meta_dict);
				} else {
					plist_from_xml(zbuf, len, &meta_dict);
				}
			} else {
				fprintf(stderr, "WARNING: could not locate %s in archive!\n", ITUNES_METADATA_PLIST_FILENAME);
			}
			if (zbuf) {
				free(zbuf);
			}

			/* determine .app directory in archive */
			zbuf = NULL;
			len = 0;
			plist_t info = NULL;
			char* filename = NULL;
			char* app_directory_name = NULL;

			if (zip_get_app_directory(zf, &app_directory_name)) {
				fprintf(stderr, "Unable to locate app directory in archive!\n");
				goto leave_cleanup;
			}

			/* construct full filename to Info.plist */
			filename = (char*)malloc(strlen(app_directory_name)+10+1);
			strcpy(filename, app_directory_name);
			free(app_directory_name);
			app_directory_name = NULL;
			strcat(filename, "Info.plist");

			if (zip_get_contents(zf, filename, 0, &zbuf, &len) < 0) {
				fprintf(stderr, "WARNING: could not locate %s in archive!\n", filename);
				free(filename);
				zip_unchange_all(zf);
				zip_close(zf);
				goto leave_cleanup;
			}
			free(filename);
			if (memcmp(zbuf, "bplist00", 8) == 0) {
				plist_from_bin(zbuf, len, &info);
			} else {
				plist_from_xml(zbuf, len, &info);
			}
			free(zbuf);

			if (!info) {
				fprintf(stderr, "Could not parse Info.plist!\n");
				zip_unchange_all(zf);
				zip_close(zf);
				goto leave_cleanup;
			}

			char *bundleexecutable = NULL;

			plist_t bname = plist_dict_get_item(info, "CFBundleExecutable");
			if (bname) {
				plist_get_string_val(bname, &bundleexecutable);
			}

			bname = plist_dict_get_item(info, "CFBundleIdentifier");
			if (bname) {
				plist_get_string_val(bname, &bundleidentifier);
			}
			plist_free(info);
			info = NULL;

			if (!bundleexecutable) {
				fprintf(stderr, "Could not determine value for CFBundleExecutable!\n");
				zip_unchange_all(zf);
				zip_close(zf);
				goto leave_cleanup;
			}

			char *sinfname = NULL;
			if (asprintf(&sinfname, "Payload/%s.app/SC_Info/%s.sinf", bundleexecutable, bundleexecutable) < 0) {
				fprintf(stderr, "Out of memory!?\n");
				goto leave_cleanup;
			}
			free(bundleexecutable);

			/* extract .sinf from package */
			zbuf = NULL;
			len = 0;
			if (zip_get_contents(zf, sinfname, 0, &zbuf, &len) == 0) {
				sinf = plist_new_data(zbuf, len);
			} else {
				fprintf(stderr, "WARNING: could not locate %s in archive!\n", sinfname);
			}
			free(sinfname);
			if (zbuf) {
				free(zbuf);
			}

			/* copy archive to device */
			pkgname = NULL;
			if (asprintf(&pkgname, "%s/%s", PKG_PATH, bundleidentifier) < 0) {
				fprintf(stderr, "Out of memory!?\n");
				goto leave_cleanup;
			}

			printf("Copying '%s' to device... ", appid);

			if (afc_upload_file(afc, appid, pkgname) < 0) {
				free(pkgname);
				goto leave_cleanup;
			}

			printf("DONE.\n");

			if (bundleidentifier) {
				instproxy_client_options_add(client_opts, "CFBundleIdentifier", bundleidentifier, NULL);
			}
			if (sinf) {
				instproxy_client_options_add(client_opts, "ApplicationSINF", sinf, NULL);
			}
			if (meta) {
				instproxy_client_options_add(client_opts, "iTunesMetadata", meta, NULL);
			}
		}
		if (zf) {
			zip_unchange_all(zf);
			zip_close(zf);
		}

		/* perform installation or upgrade */
		if (cmd == CMD_INSTALL) {
			printf("Installing '%s'\n", bundleidentifier);
#ifdef HAVE_LIBIMOBILEDEVICE_1_1
			instproxy_install(ipc, pkgname, client_opts, status_cb, NULL);
#else
			instproxy_install(ipc, pkgname, client_opts, status_cb);
#endif
		} else {
			printf("Upgrading '%s'\n", bundleidentifier);
#ifdef HAVE_LIBIMOBILEDEVICE_1_1
			instproxy_upgrade(ipc, pkgname, client_opts, status_cb, NULL);
#else
			instproxy_upgrade(ipc, pkgname, client_opts, status_cb);
#endif
		}
		instproxy_client_options_free(client_opts);
		free(pkgname);
		wait_for_op_complete = 1;
		notification_expected = 1;
	} else if (cmd == CMD_UNINSTALL) {
		printf("Uninstalling '%s'\n", appid);
#ifdef HAVE_LIBIMOBILEDEVICE_1_1
		instproxy_uninstall(ipc, appid, NULL, status_cb, NULL);
#else
		instproxy_uninstall(ipc, appid, NULL, status_cb);
#endif
		wait_for_op_complete = 1;
		notification_expected = 0;
	} else if (cmd == CMD_LIST_ARCHIVES) {
		int xml_mode = 0;
		plist_t dict = NULL;
		plist_t lres = NULL;

		/* look for options */
		if (options) {
			char *opts = strdup(options);
			char *elem = strtok(opts, ",");
			while (elem) {
				if (!strcmp(elem, "xml")) {
					xml_mode = 1;
				}
				elem = strtok(NULL, ",");
			}
		}

		err = instproxy_lookup_archives(ipc, NULL, &dict);
		if (err != INSTPROXY_E_SUCCESS) {
			fprintf(stderr, "ERROR: lookup_archives returned %d\n", err);
			goto leave_cleanup;
		}
		if (!dict) {
			fprintf(stderr,
					"ERROR: lookup_archives did not return a plist!?\n");
			goto leave_cleanup;
		}

		lres = plist_dict_get_item(dict, "LookupResult");
		if (!lres || (plist_get_node_type(lres) != PLIST_DICT)) {
			plist_free(dict);
			fprintf(stderr, "ERROR: Could not get dict 'LookupResult'\n");
			goto leave_cleanup;
		}

		if (xml_mode) {
			char *xml = NULL;
			uint32_t len = 0;

			plist_to_xml(lres, &xml, &len);
			if (xml) {
				puts(xml);
				free(xml);
			}
			plist_free(dict);
			goto leave_cleanup;
		}
		plist_dict_iter iter = NULL;
		plist_t node = NULL;
		char *key = NULL;

		printf("Total: %d archived apps\n", plist_dict_get_size(lres));
		plist_dict_new_iter(lres, &iter);
		if (!iter) {
			plist_free(dict);
			fprintf(stderr, "ERROR: Could not create plist_dict_iter!\n");
			goto leave_cleanup;
		}
		do {
			key = NULL;
			node = NULL;
			plist_dict_next_item(lres, iter, &key, &node);
			if (key && (plist_get_node_type(node) == PLIST_DICT)) {
				char *s_dispName = NULL;
				char *s_version = NULL;
				plist_t dispName =
					plist_dict_get_item(node, "CFBundleDisplayName");
				plist_t version =
					plist_dict_get_item(node, "CFBundleVersion");
				if (dispName) {
					plist_get_string_val(dispName, &s_dispName);
				}
				if (version) {
					plist_get_string_val(version, &s_version);
				}
				if (!s_dispName) {
					s_dispName = strdup(key);
				}
				if (s_version) {
					printf("%s - %s %s\n", key, s_dispName, s_version);
					free(s_version);
				} else {
					printf("%s - %s\n", key, s_dispName);
				}
				free(s_dispName);
				free(key);
			}
		}
		while (node);
		plist_free(dict);
	} else if (cmd == CMD_ARCHIVE) {
		char *copy_path = NULL;
		int remove_after_copy = 0;
		int skip_uninstall = 1;
		int app_only = 0;
		int docs_only = 0;
		plist_t client_opts = NULL;

		/* look for options */
		if (options) {
			char *opts = strdup(options);
			char *elem = strtok(opts, ",");
			while (elem) {
				if (!strcmp(elem, "uninstall")) {
					skip_uninstall = 0;
				} else if (!strcmp(elem, "app_only")) {
					app_only = 1;
					docs_only = 0;
				} else if (!strcmp(elem, "docs_only")) {
					docs_only = 1;
					app_only = 0;
				} else if ((strlen(elem) > 5) && !strncmp(elem, "copy=", 5)) {
					copy_path = strdup(elem+5);
				} else if (!strcmp(elem, "remove")) {
					remove_after_copy = 1;
				}
				elem = strtok(NULL, ",");
			}
		}

		if (skip_uninstall || app_only || docs_only) {
			client_opts = instproxy_client_options_new();
			if (skip_uninstall) {
				instproxy_client_options_add(client_opts, "SkipUninstall", 1, NULL);
			}
			if (app_only) {
				instproxy_client_options_add(client_opts, "ArchiveType", "ApplicationOnly", NULL);
			} else if (docs_only) {
				instproxy_client_options_add(client_opts, "ArchiveType", "DocumentsOnly", NULL);
			}
		}

		if (copy_path) {
			struct stat fst;
			if (stat(copy_path, &fst) != 0) {
				fprintf(stderr, "ERROR: stat: %s: %s\n", copy_path, strerror(errno));
				free(copy_path);
				goto leave_cleanup;
			}

			if (!S_ISDIR(fst.st_mode)) {
				fprintf(stderr, "ERROR: '%s' is not a directory as expected.\n", copy_path);
				free(copy_path);
				goto leave_cleanup;
			}

#ifdef HAVE_LIBIMOBILEDEVICE_1_1_5
			if (service) {
				lockdownd_service_descriptor_free(service);
			}
			service = NULL;
#else
			service = 0;
#endif
			if ((lockdownd_start_service(client, "com.apple.afc", &service) != LOCKDOWN_E_SUCCESS) || !service) {
				fprintf(stderr, "Could not start com.apple.afc!\n");
				free(copy_path);
				goto leave_cleanup;
			}

			lockdownd_client_free(client);
			client = NULL;

			if (afc_client_new(phone, service, &afc) != INSTPROXY_E_SUCCESS) {
				fprintf(stderr, "Could not connect to AFC!\n");
				goto leave_cleanup;
			}
		}

#ifdef HAVE_LIBIMOBILEDEVICE_1_1
		instproxy_archive(ipc, appid, client_opts, status_cb, NULL);
#else
		instproxy_archive(ipc, appid, client_opts, status_cb);
#endif
		instproxy_client_options_free(client_opts);
		wait_for_op_complete = 1;
		if (skip_uninstall) {
			notification_expected = 0;
		} else {
			notification_expected = 1;
		}

		idevice_wait_for_operation_to_complete();

		if (copy_path) {
			if (err_occured) {
				afc_client_free(afc);
				afc = NULL;
				goto leave_cleanup;
			}
			FILE *f = NULL;
			uint64_t af = 0;
			/* local filename */
			char *localfile = NULL;
			if (asprintf(&localfile, "%s/%s.ipa", copy_path, appid) < 0) {
				fprintf(stderr, "Out of memory!?\n");
				goto leave_cleanup;
			}
			free(copy_path);

			f = fopen(localfile, "wb");
			if (!f) {
				fprintf(stderr, "ERROR: fopen: %s: %s\n", localfile, strerror(errno));
				free(localfile);
				goto leave_cleanup;
			}

			/* remote filename */
			char *remotefile = NULL;
			if (asprintf(&remotefile, "%s/%s.zip", APPARCH_PATH, appid) < 0) {
				fprintf(stderr, "Out of memory!?\n");
				goto leave_cleanup;
			}

			uint32_t fsize = 0;
			char **fileinfo = NULL;
			if ((afc_get_file_info(afc, remotefile, &fileinfo) != AFC_E_SUCCESS) || !fileinfo) {
				fprintf(stderr, "ERROR getting AFC file info for '%s' on device!\n", remotefile);
				fclose(f);
				free(remotefile);
				free(localfile);
				goto leave_cleanup;
			}

			int i;
			for (i = 0; fileinfo[i]; i+=2) {
				if (!strcmp(fileinfo[i], "st_size")) {
					fsize = atoi(fileinfo[i+1]);
					break;
				}
			}
			i = 0;
			while (fileinfo[i]) {
				free(fileinfo[i]);
				i++;
			}
			free(fileinfo);

			if (fsize == 0) {
				fprintf(stderr, "Hm... remote file length could not be determined. Cannot copy.\n");
				fclose(f);
				free(remotefile);
				free(localfile);
				goto leave_cleanup;
			}

			if ((afc_file_open(afc, remotefile, AFC_FOPEN_RDONLY, &af) != AFC_E_SUCCESS) || !af) {
				fclose(f);
				fprintf(stderr, "ERROR: could not open '%s' on device for reading!\n", remotefile);
				free(remotefile);
				free(localfile);
				goto leave_cleanup;
			}

			/* copy file over */
			printf("Copying '%s' --> '%s'... ", remotefile, localfile);
			free(remotefile);
			free(localfile);

			uint32_t amount = 0;
			uint32_t total = 0;
			char buf[8192];

			do {
				if (afc_file_read(afc, af, buf, sizeof(buf), &amount) != AFC_E_SUCCESS) {
					fprintf(stderr, "AFC Read error!\n");
					break;
				}

				if (amount > 0) {
					size_t written = fwrite(buf, 1, amount, f);
					if (written != amount) {
						fprintf(stderr, "Error when writing %d bytes to local file!\n", amount);
						break;
					}
					total += written;
				}
			} while (amount > 0);

			afc_file_close(afc, af);
			fclose(f);

			printf("DONE.\n");

			if (total != fsize) {
				fprintf(stderr, "WARNING: remote and local file sizes don't match (%d != %d)\n", fsize, total);
				if (remove_after_copy) {
					fprintf(stderr, "NOTE: archive file will NOT be removed from device\n");
					remove_after_copy = 0;
				}
			}

			if (remove_after_copy) {
				/* remove archive if requested */
				printf("Removing '%s'\n", appid);
				cmd = CMD_REMOVE_ARCHIVE;
				free(options);
				options = NULL;
				if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "ideviceinstaller")) {
					fprintf(stderr, "Could not connect to lockdownd. Exiting.\n");
					goto leave_cleanup;
				}
				goto run_again;
			}
		}
		goto leave_cleanup;
	} else if (cmd == CMD_RESTORE) {
#ifdef HAVE_LIBIMOBILEDEVICE_1_1
		instproxy_restore(ipc, appid, NULL, status_cb, NULL);
#else
		instproxy_restore(ipc, appid, NULL, status_cb);
#endif
		wait_for_op_complete = 1;
		notification_expected = 1;
	} else if (cmd == CMD_REMOVE_ARCHIVE) {
#ifdef HAVE_LIBIMOBILEDEVICE_1_1
		instproxy_remove_archive(ipc, appid, NULL, status_cb, NULL);
#else
		instproxy_remove_archive(ipc, appid, NULL, status_cb);
#endif
		wait_for_op_complete = 1;
	} else {
		printf
			("ERROR: no operation selected?! This should not be reached!\n");
		res = -2;
		goto leave_cleanup;
	}

	if (client) {
		/* not needed anymore */
		lockdownd_client_free(client);
		client = NULL;
	}

	idevice_wait_for_operation_to_complete();

leave_cleanup:
	if (bundleidentifier) {
		free(bundleidentifier);
	}
	if (np) {
		np_client_free(np);
	}
	if (ipc) {
		instproxy_client_free(ipc);
	}
	if (afc) {
		afc_client_free(afc);
	}
	if (client) {
		lockdownd_client_free(client);
	}
	idevice_free(phone);

	if (udid) {
		free(udid);
	}
	if (appid) {
		free(appid);
	}
	if (options) {
		free(options);
	}

	return res;
}