From 5086a9751f5c1298ac423a52b63ca299130aa1c2 Mon Sep 17 00:00:00 2001 From: Nikias Bassen Date: Wed, 28 Aug 2019 03:06:18 +0200 Subject: Add preboardservice_v2 implementation --- include/Makefile.am | 1 + include/libimobiledevice/preboard.h | 168 +++++++++++++++++++ src/Makefile.am | 1 + src/preboard.c | 318 ++++++++++++++++++++++++++++++++++++ src/preboard.h | 34 ++++ 5 files changed, 522 insertions(+) create mode 100644 include/libimobiledevice/preboard.h create mode 100644 src/preboard.c create mode 100644 src/preboard.h diff --git a/include/Makefile.am b/include/Makefile.am index f2b93ed..eaf052a 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -21,5 +21,6 @@ nobase_include_HEADERS = libimobiledevice/libimobiledevice.h \ libimobiledevice/debugserver.h\ libimobiledevice/syslog_relay.h\ libimobiledevice/mobileactivation.h\ + libimobiledevice/preboard.h\ libimobiledevice/property_list_service.h\ libimobiledevice/service.h diff --git a/include/libimobiledevice/preboard.h b/include/libimobiledevice/preboard.h new file mode 100644 index 0000000..dc4e5f3 --- /dev/null +++ b/include/libimobiledevice/preboard.h @@ -0,0 +1,168 @@ +/** + * @file libimobiledevice/preboard.h + * @brief Service to 'preboard' a device, which allows to ask for passcode during firmware updates. + * \internal + * + * Copyright (c) 2019 Nikias Bassen, 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 + */ + +#ifndef IPREBOARD_H +#define IPREBOARD_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define PREBOARD_SERVICE_NAME "com.apple.preboardservice_v2" + +/** Error Codes */ +typedef enum { + PREBOARD_E_SUCCESS = 0, + PREBOARD_E_INVALID_ARG = -1, + PREBOARD_E_PLIST_ERROR = -2, + PREBOARD_E_MUX_ERROR = -3, + PREBOARD_E_SSL_ERROR = -4, + PREBOARD_E_NOT_ENOUGH_DATA = -5, + PREBOARD_E_TIMEOUT = -6, + PREBOARD_E_OP_IN_PROGRESS = -10, + PREBOARD_E_UNKNOWN_ERROR = -256 +} preboard_error_t; + +typedef struct preboard_client_private preboard_client_private; +typedef preboard_client_private *preboard_client_t; /**< The client handle. */ + +/** Reports the status response of the given command */ +typedef void (*preboard_status_cb_t) (plist_t message, void *user_data); + +/** + * Connects to the preboard service on the specified device. + * + * @param device The device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. + * @param client Pointer that will point to a newly allocated + * preboard_client_t upon successful return. Must be freed using + * preboard_client_free() after use. + * + * @return PREBOARD_E_SUCCESS on success, PREBOARD_E_INVALID_ARG when + * client is NULL, or an PREBOARD_E_* error code otherwise. + */ +preboard_error_t preboard_client_new(idevice_t device, lockdownd_service_descriptor_t service, preboard_client_t * client); + +/** + * Starts a new preboard service on the specified device and connects to it. + * + * @param device The device to connect to. + * @param client Pointer that will point to a newly allocated + * preboard_client_t upon successful return. Must be freed using + * preboard_client_free() after use. + * @param label The label to use for communication. Usually the program name. + * Pass NULL to disable sending the label in requests to lockdownd. + * + * @return PREBOARD_E_SUCCESS on success, or a PREBOARD_E_* error + * code otherwise. + */ +preboard_error_t preboard_client_start_service(idevice_t device, preboard_client_t * client, const char* label); + +/** + * Disconnects a preboard client from the device and frees up the + * preboard client data. + * + * @param client The preboard client to disconnect and free. + * + * @return PREBOARD_E_SUCCESS on success, PREBOARD_E_INVALID_ARG when + * client is NULL, or a PREBOARD_E_* error code otherwise. + */ +preboard_error_t preboard_client_free(preboard_client_t client); + +/** + * Sends a plist to the service. + * + * @param client The preboard client + * @param plist The plist to send + * + * @return PREBOARD_E_SUCCESS on success, + * PREBOARD_E_INVALID_ARG when client or plist is NULL, + * or a PREBOARD_E_* error code on error + */ +preboard_error_t preboard_send(preboard_client_t client, plist_t plist); + +/** + * Receives a plist from the service. + * + * @param client The preboard client + * @param plist Pointer to a plist_t what will be set to the received plist + * + * @return PREBOARD_E_SUCCESS on success, + * PREBOARD_E_INVALID_ARG when client or plist is NULL, + * PREBOARD_E_TIMEOUT when no data was received after 5 seconds, + * or a PREBOARD_E_* error code on error + */ +preboard_error_t preboard_receive(preboard_client_t client, plist_t * plist); + +/** + * Receives a plist from the service with the specified timeout. + * + * @param client The preboard client + * @param plist Pointer to a plist_t what will be set to the received plist + * + * @return PREBOARD_E_SUCCESS on success, + * PREBOARD_E_INVALID_ARG when client or plist is NULL, + * PREBOARD_E_TIMEOUT when no data was received after the given timeout, + * or a PREBOARD_E_* error code on error. + */ +preboard_error_t preboard_receive_with_timeout(preboard_client_t client, plist_t * plist, uint32_t timeout_ms); + +/** + * Tells the preboard service to create a stashbag. This will make the device + * show a passcode entry so it can generate and store a token that is later + * used during restore. + * + * @param client The preboard client + * @param manifest An optional manifest + * @param status_cb Callback function that will receive status and error messages. + * Can be NULL if you want to handle receiving messages in your own code. + * @param user_data User data for callback function or NULL. + * + * @return PREBOARD_E_SUCCESS if the command was successfully submitted, + * PREBOARD_E_INVALID_ARG when client is invalid, + * or a PREBOARD_E_* error code on error. + */ +preboard_error_t preboard_create_stashbag(preboard_client_t client, plist_t manifest, preboard_status_cb_t status_cb, void *user_data); + +/** + * Instructs the preboard service to commit a previously created stashbag. + * + * @param client The preboard client to use for receiving + * @param manifest An optional manifest + * @param status_cb Callback function that will receive status and error messages + * Can be NULL if you want to handle receiving messages in your own code. + * @param user_data User data for callback function or NULL. + * + * @return PREBOARD_E_SUCCESS if the command was successfully submitted, + * PREBOARD_E_INVALID_ARG when client is invalid, + * or a PREBOARD_E_* error code on error. + */ +preboard_error_t preboard_commit_stashbag(preboard_client_t client, plist_t manifest, preboard_status_cb_t status_cb, void *user_data); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/Makefile.am b/src/Makefile.am index fcde8ae..5fcf097 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -29,6 +29,7 @@ libimobiledevice_la_SOURCES = idevice.c idevice.h \ debugserver.c debugserver.h\ webinspector.c webinspector.h\ mobileactivation.c mobileactivation.h\ + preboard.c preboard.h \ syslog_relay.c syslog_relay.h if WIN32 diff --git a/src/preboard.c b/src/preboard.c new file mode 100644 index 0000000..7b27a34 --- /dev/null +++ b/src/preboard.c @@ -0,0 +1,318 @@ +/* + * preboard.c + * com.apple.preboardservice_v2 service implementation. + * + * Copyright (c) 2019 Nikias Bassen, 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 +#endif +#include +#include +#include + +#include "preboard.h" +#include "lockdown.h" +#include "common/debug.h" + +/** + * Convert a property_list_service_error_t value to a preboard_error_t value. + * Used internally to get correct error codes. + * + * @param err An property_list_service_error_t error code + * + * @return A matching preboard_error_t error code, + * PREBOARD_E_UNKNOWN_ERROR otherwise. + */ +static preboard_error_t preboard_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return PREBOARD_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return PREBOARD_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return PREBOARD_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return PREBOARD_E_MUX_ERROR; + case PROPERTY_LIST_SERVICE_E_SSL_ERROR: + return PREBOARD_E_SSL_ERROR; + case PROPERTY_LIST_SERVICE_E_NOT_ENOUGH_DATA: + return PREBOARD_E_NOT_ENOUGH_DATA; + case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT: + return PREBOARD_E_TIMEOUT; + default: + break; + } + return PREBOARD_E_UNKNOWN_ERROR; +} + +LIBIMOBILEDEVICE_API preboard_error_t preboard_client_new(idevice_t device, lockdownd_service_descriptor_t service, preboard_client_t * client) +{ + *client = NULL; + + if (!device || !service || service->port == 0 || !client || *client) { + debug_info("Incorrect parameter passed to preboard_client_new."); + return PREBOARD_E_INVALID_ARG; + } + + debug_info("Creating preboard_client, port = %d.", service->port); + + property_list_service_client_t plclient = NULL; + preboard_error_t ret = preboard_error(property_list_service_client_new(device, service, &plclient)); + if (ret != PREBOARD_E_SUCCESS) { + debug_info("Creating a property list client failed. Error: %i", ret); + return ret; + } + + preboard_client_t client_loc = (preboard_client_t) malloc(sizeof(struct preboard_client_private)); + client_loc->parent = plclient; + client_loc->receive_status_thread = THREAD_T_NULL; + + *client = client_loc; + + debug_info("preboard_client successfully created."); + return 0; +} + +LIBIMOBILEDEVICE_API preboard_error_t preboard_client_start_service(idevice_t device, preboard_client_t * client, const char* label) +{ + preboard_error_t err = PREBOARD_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, PREBOARD_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(preboard_client_new), &err); + return err; +} + +LIBIMOBILEDEVICE_API preboard_error_t preboard_client_free(preboard_client_t client) +{ + if (!client) + return PREBOARD_E_INVALID_ARG; + + property_list_service_client_t parent = client->parent; + client->parent = NULL; + if (client->receive_status_thread) { + debug_info("joining receive_status_thread"); + thread_join(client->receive_status_thread); + thread_free(client->receive_status_thread); + client->receive_status_thread = THREAD_T_NULL; + } + preboard_error_t err = preboard_error(property_list_service_client_free(parent)); + free(client); + + return err; +} + +LIBIMOBILEDEVICE_API preboard_error_t preboard_send(preboard_client_t client, plist_t plist) +{ + preboard_error_t res = PREBOARD_E_UNKNOWN_ERROR; + res = preboard_error(property_list_service_send_binary_plist(client->parent, plist)); + if (res != PREBOARD_E_SUCCESS) { + debug_info("Sending plist failed with error %d", res); + return res; + } + return res; +} + +LIBIMOBILEDEVICE_API preboard_error_t preboard_receive_with_timeout(preboard_client_t client, plist_t * plist, uint32_t timeout_ms) +{ + preboard_error_t res = PREBOARD_E_UNKNOWN_ERROR; + plist_t outplist = NULL; + res = preboard_error(property_list_service_receive_plist_with_timeout(client->parent, &outplist, timeout_ms)); + if (res != PREBOARD_E_SUCCESS && res != PREBOARD_E_TIMEOUT) { + debug_info("Could not receive plist, error %d", res); + plist_free(outplist); + } else if (res == PREBOARD_E_SUCCESS) { + *plist = outplist; + } + return res; +} + +LIBIMOBILEDEVICE_API preboard_error_t preboard_receive(preboard_client_t client, plist_t * plist) +{ + return preboard_receive_with_timeout(client, plist, 5000); +} + +struct preboard_status_data { + preboard_client_t client; + preboard_status_cb_t cbfunc; + void *user_data; +}; + +static void* preboard_receive_status_loop_thread(void* arg) +{ + struct preboard_status_data *data = (struct preboard_status_data*)arg; + + /* run until the service disconnects or an error occurs */ + while (data->client && data->client->parent) { + plist_t pl = NULL; + preboard_error_t perr = preboard_receive_with_timeout(data->client, &pl, 1000); + if (perr == PREBOARD_E_TIMEOUT) { + continue; + } else if (perr == PREBOARD_E_SUCCESS) { + data->cbfunc(pl, data->user_data); + } + plist_free(pl); + if (perr != PREBOARD_E_SUCCESS) { + data->cbfunc(NULL, data->user_data); + break; + } + } + + /* cleanup */ + debug_info("done, cleaning up."); + + if (data->client->receive_status_thread) { + thread_free(data->client->receive_status_thread); + data->client->receive_status_thread = THREAD_T_NULL; + } + free(data); + + return NULL; +} + +static preboard_error_t preboard_receive_status_loop_with_callback(preboard_client_t client, preboard_status_cb_t status_cb, void *user_data) +{ + if (!client || !client->parent) { + return PREBOARD_E_INVALID_ARG; + } + + if (client->receive_status_thread) { + return PREBOARD_E_OP_IN_PROGRESS; + } + + preboard_error_t res = PREBOARD_E_UNKNOWN_ERROR; + struct preboard_status_data *data = (struct preboard_status_data*)malloc(sizeof(struct preboard_status_data)); + if (data) { + data->client = client; + data->cbfunc = status_cb; + data->user_data = user_data; + if (thread_new(&client->receive_status_thread, preboard_receive_status_loop_thread, data) == 0) { + res = PREBOARD_E_SUCCESS; + } + } + + return res; +} + +LIBIMOBILEDEVICE_API preboard_error_t preboard_create_stashbag(preboard_client_t client, plist_t manifest, preboard_status_cb_t status_cb, void *user_data) +{ + if (!client) { + return PREBOARD_E_INVALID_ARG; + } + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("CreateStashbag")); + if (manifest) { + plist_dict_set_item(dict, "Manifest", plist_copy(manifest)); + } + preboard_error_t perr = preboard_send(client, dict); + plist_free(dict); + if (perr != PREBOARD_E_SUCCESS) { + return perr; + } + if (!status_cb) { + return PREBOARD_E_SUCCESS; + } + + return preboard_receive_status_loop_with_callback(client, status_cb, user_data); + + // return { ShowDialog: true} or {Timeout: true} followed by {HideDialog: true} + // or { Error: 1, ErrorString: } + + +/* + + + + + ShowDialog + + Version + 2 + + + +for success, it will send the HideDialog message, then wait up to 14400 seconds (4h) for the device to reboot? + + + + + + + + + Error + 1 + ErrorString + user authentication failed: Error Domain=com.apple.LocalAuthentication Code=-2 "Canceled by user." UserInfo={BiometryType=1, NSLocalizedDescription=Canceled by user.} + Version + 2 + + + + + + + + + + Timeout + + Version + 2 + + + + + + + + HideDialog + + Version + 2 + + + +*/ +} + +LIBIMOBILEDEVICE_API preboard_error_t preboard_commit_stashbag(preboard_client_t client, plist_t manifest, preboard_status_cb_t status_cb, void *user_data) +{ + // returns { StashbagCommitComplete: true } + // or { StashbagCommitComplete: 0, Error: 1, ErrorString: } + + if (!client) { + return PREBOARD_E_INVALID_ARG; + } + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("CommitStashbag")); + if (manifest) { + plist_dict_set_item(dict, "Manifest", plist_copy(manifest)); + } + preboard_error_t perr = preboard_send(client, dict); + plist_free(dict); + if (perr != PREBOARD_E_SUCCESS) { + return perr; + } + if (!status_cb) { + return PREBOARD_E_SUCCESS; + } + + return preboard_receive_status_loop_with_callback(client, status_cb, user_data); +} diff --git a/src/preboard.h b/src/preboard.h new file mode 100644 index 0000000..c5143a9 --- /dev/null +++ b/src/preboard.h @@ -0,0 +1,34 @@ +/* + * preboard.h + * com.apple.preboard_v2 service header file. + * + * Copyright (c) 2019 Nikias Bassen, 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 + */ + +#ifndef __PREBOARD_H +#define __PREBOARD_H + +#include "libimobiledevice/preboard.h" +#include "property_list_service.h" +#include "common/thread.h" + +struct preboard_client_private { + property_list_service_client_t parent; + THREAD_T receive_status_thread; +}; + +#endif -- cgit v1.1-32-gdbae