diff options
Diffstat (limited to 'src/client.c')
-rw-r--r-- | src/client.c | 1058 |
1 files changed, 1058 insertions, 0 deletions
diff --git a/src/client.c b/src/client.c new file mode 100644 index 0000000..dbbdd5f --- /dev/null +++ b/src/client.c @@ -0,0 +1,1058 @@ +/* + * client.c + * + * Copyright (C) 2009 Hector Martin <hector@marcansoft.com> + * Copyright (C) 2009 Nikias Bassen <nikias@gmx.li> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or version 3. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; 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 + +#define _GNU_SOURCE 1 + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <sys/un.h> +#include <arpa/inet.h> +#include <fcntl.h> + +#include <plist/plist.h> +#include <libimobiledevice-glue/collection.h> +#include <libimobiledevice-glue/thread.h> + +#include "log.h" +#include "usb.h" +#include "client.h" +#include "device.h" +#include "conf.h" + +#define CMD_BUF_SIZE 0x10000 +#define REPLY_BUF_SIZE 0x10000 + +enum client_state { + CLIENT_COMMAND, // waiting for command + CLIENT_LISTEN, // listening for devices + CLIENT_CONNECTING1, // issued connection request + CLIENT_CONNECTING2, // connection established, but waiting for response message to get sent + CLIENT_CONNECTED, // connected + CLIENT_DEAD +}; + +struct mux_client { + int fd; + unsigned char *ob_buf; + uint32_t ob_size; + uint32_t ob_capacity; + unsigned char *ib_buf; + uint32_t ib_size; + uint32_t ib_capacity; + short events, devents; + uint32_t connect_tag; + int connect_device; + enum client_state state; + uint32_t proto_version; + uint32_t number; + plist_t info; +}; + +static struct collection client_list; +mutex_t client_list_mutex; +static uint32_t client_number = 0; + +#ifdef SO_PEERCRED +static char* _get_process_name_by_pid(const int pid) +{ + char* name = (char*)calloc(1024, sizeof(char)); + if(name) { + sprintf(name, "/proc/%d/cmdline", pid); + FILE* f = fopen(name, "r"); + if(f) { + size_t size; + size = fread(name, sizeof(char), 1024, f); + if(size > 0) { + if('\n' == name[size-1]) + name[size-1]='\0'; + } + fclose(f); + } + } + return name; +} +#endif + +/** + * Receive raw data from the client socket. + * + * @param client Client to read from. + * @param buffer Buffer to store incoming data. + * @param len Max number of bytes to read. + * @return Same as recv() system call. Number of bytes read; when < 0 errno will be set. + */ +int client_read(struct mux_client *client, void *buffer, uint32_t len) +{ + usbmuxd_log(LL_SPEW, "client_read fd %d buf %p len %d", client->fd, buffer, len); + if(client->state != CLIENT_CONNECTED) { + usbmuxd_log(LL_ERROR, "Attempted to read from client %d not in CONNECTED state", client->fd); + return -1; + } + return recv(client->fd, buffer, len, 0); +} + +/** + * Send raw data to the client socket. + * + * @param client Client to send to. + * @param buffer The data to send. + * @param len Number of bytes to write. + * @return Same as system call send(). Number of bytes written; when < 0 errno will be set. + */ +int client_write(struct mux_client *client, void *buffer, uint32_t len) +{ + int sret = -1; + + usbmuxd_log(LL_SPEW, "client_write fd %d buf %p len %d", client->fd, buffer, len); + if(client->state != CLIENT_CONNECTED) { + usbmuxd_log(LL_ERROR, "Attempted to write to client %d not in CONNECTED state", client->fd); + return -1; + } + + sret = send(client->fd, buffer, len, 0); + if (sret < 0) { + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { + usbmuxd_log(LL_DEBUG, "client_write: fd %d not ready for writing", client->fd); + sret = 0; + } else { + usbmuxd_log(LL_ERROR, "ERROR: client_write: sending to fd %d failed: %s", client->fd, strerror(errno)); + } + } + return sret; +} + +/** + * Set event mask to use for ppoll()ing the client socket. + * Typically POLLOUT and/or POLLIN. Note that this overrides + * the current mask, that is, it is not ORing the argument + * into the current mask. + * + * @param client The client to set the event mask on. + * @param events The event mask to sert. + * @return 0 on success, -1 on error. + */ +int client_set_events(struct mux_client *client, short events) +{ + if((client->state != CLIENT_CONNECTED) && (client->state != CLIENT_CONNECTING2)) { + usbmuxd_log(LL_ERROR, "client_set_events to client %d not in CONNECTED state", client->fd); + return -1; + } + client->devents = events; + if(client->state == CLIENT_CONNECTED) + client->events = events; + return 0; +} + +/** + * Wait for an inbound connection on the usbmuxd socket + * and create a new mux_client instance for it, and store + * the client in the client list. + * + * @param listenfd the socket fd to accept() on. + * @return The connection fd for the client, or < 0 for error + * in which case errno will be set. + */ +int client_accept(int listenfd) +{ + struct sockaddr_un addr; + int cfd; + socklen_t len = sizeof(struct sockaddr_un); + cfd = accept(listenfd, (struct sockaddr *)&addr, &len); + if (cfd < 0) { + usbmuxd_log(LL_ERROR, "accept() failed (%s)", strerror(errno)); + return cfd; + } + + int flags = fcntl(cfd, F_GETFL, 0); + if (flags < 0) { + usbmuxd_log(LL_ERROR, "ERROR: Could not get socket flags!"); + } else { + if (fcntl(cfd, F_SETFL, flags | O_NONBLOCK) < 0) { + usbmuxd_log(LL_ERROR, "ERROR: Could not set socket to non-blocking mode"); + } + } + + int bufsize = 0x20000; + if (setsockopt(cfd, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(int)) == -1) { + usbmuxd_log(LL_WARNING, "Could not set send buffer for client socket"); + } + if (setsockopt(cfd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(int)) == -1) { + usbmuxd_log(LL_WARNING, "Could not set receive buffer for client socket"); + } + + int yes = 1; + setsockopt(cfd, IPPROTO_TCP, TCP_NODELAY, (void*)&yes, sizeof(int)); + + struct mux_client *client; + client = malloc(sizeof(struct mux_client)); + memset(client, 0, sizeof(struct mux_client)); + + client->fd = cfd; + client->ob_buf = malloc(REPLY_BUF_SIZE); + client->ob_size = 0; + client->ob_capacity = REPLY_BUF_SIZE; + client->ib_buf = malloc(CMD_BUF_SIZE); + client->ib_size = 0; + client->ib_capacity = CMD_BUF_SIZE; + client->state = CLIENT_COMMAND; + client->events = POLLIN; + client->info = NULL; + + mutex_lock(&client_list_mutex); + client->number = client_number++; + collection_add(&client_list, client); + mutex_unlock(&client_list_mutex); + +#ifdef SO_PEERCRED + if (log_level >= LL_INFO) { + struct ucred cr; + len = sizeof(struct ucred); + getsockopt(client->fd, SOL_SOCKET, SO_PEERCRED, &cr, &len); + + if (getpid() == cr.pid) { + usbmuxd_log(LL_INFO, "Client %d accepted: %s[%d]", client->fd, PACKAGE_NAME, cr.pid); + } else { + char* process_name = _get_process_name_by_pid(cr.pid); + usbmuxd_log(LL_INFO, "Client %d accepted: %s[%d]", client->fd, process_name, cr.pid); + free(process_name); + } + } +#else + usbmuxd_log(LL_INFO, "Client %d accepted", client->fd); +#endif + return client->fd; +} + +void client_close(struct mux_client *client) +{ + int found = 0; + mutex_lock(&client_list_mutex); + FOREACH(struct mux_client *lc, &client_list) { + if (client == lc) { + found = 1; + break; + } + } ENDFOREACH + if (!found) { + // in case we get called again but client was already freed + usbmuxd_log(LL_DEBUG, "%s: ignoring for non-existing client %p", __func__, client); + mutex_unlock(&client_list_mutex); + return; + } +#ifdef SO_PEERCRED + if (log_level >= LL_INFO) { + struct ucred cr; + socklen_t len = sizeof(struct ucred); + getsockopt(client->fd, SOL_SOCKET, SO_PEERCRED, &cr, &len); + + if (getpid() == cr.pid) { + usbmuxd_log(LL_INFO, "Client %d is going to be disconnected: %s[%d]", client->fd, PACKAGE_NAME, cr.pid); + } else { + char* process_name = _get_process_name_by_pid(cr.pid); + usbmuxd_log(LL_INFO, "Client %d is going to be disconnected: %s[%d]", client->fd, process_name, cr.pid); + free(process_name); + } + } +#else + usbmuxd_log(LL_INFO, "Client %d is going to be disconnected", client->fd); +#endif + if(client->state == CLIENT_CONNECTING1 || client->state == CLIENT_CONNECTING2) { + usbmuxd_log(LL_INFO, "Client died mid-connect, aborting device %d connection", client->connect_device); + client->state = CLIENT_DEAD; + device_abort_connect(client->connect_device, client); + } + close(client->fd); + free(client->ob_buf); + free(client->ib_buf); + plist_free(client->info); + + collection_remove(&client_list, client); + mutex_unlock(&client_list_mutex); + free(client); +} + +void client_get_fds(struct fdlist *list) +{ + mutex_lock(&client_list_mutex); + FOREACH(struct mux_client *client, &client_list) { + fdlist_add(list, FD_CLIENT, client->fd, client->events); + } ENDFOREACH + mutex_unlock(&client_list_mutex); +} + +static int output_buffer_add_message(struct mux_client *client, uint32_t tag, enum usbmuxd_msgtype msg, void *payload, int payload_length) +{ + struct usbmuxd_header hdr; + hdr.version = client->proto_version; + hdr.length = sizeof(hdr) + payload_length; + hdr.message = msg; + hdr.tag = tag; + usbmuxd_log(LL_DEBUG, "Client %d output buffer got tag %d msg %d payload_length %d", client->fd, tag, msg, payload_length); + + uint32_t available = client->ob_capacity - client->ob_size; + /* the output buffer _should_ be large enough, but just in case */ + if(available < hdr.length) { + unsigned char* new_buf; + uint32_t new_size = ((client->ob_capacity + hdr.length + 4096) / 4096) * 4096; + usbmuxd_log(LL_DEBUG, "%s: Enlarging client %d output buffer %d -> %d", __func__, client->fd, client->ob_capacity, new_size); + new_buf = realloc(client->ob_buf, new_size); + if (!new_buf) { + usbmuxd_log(LL_FATAL, "%s: Failed to realloc.", __func__); + return -1; + } + client->ob_buf = new_buf; + client->ob_capacity = new_size; + } + memcpy(client->ob_buf + client->ob_size, &hdr, sizeof(hdr)); + if(payload && payload_length) + memcpy(client->ob_buf + client->ob_size + sizeof(hdr), payload, payload_length); + client->ob_size += hdr.length; + client->events |= POLLOUT; + return hdr.length; +} + +static int send_plist(struct mux_client *client, uint32_t tag, plist_t plist) +{ + int res = -1; + char *xml = NULL; + uint32_t xmlsize = 0; + plist_to_xml(plist, &xml, &xmlsize); + if (xml) { + res = output_buffer_add_message(client, tag, MESSAGE_PLIST, xml, xmlsize); + free(xml); + } else { + usbmuxd_log(LL_ERROR, "%s: Could not convert plist to xml", __func__); + } + return res; +} + +static int send_result(struct mux_client *client, uint32_t tag, uint32_t result) +{ + int res = -1; + if (client->proto_version == 1) { + /* XML plist packet */ + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "MessageType", plist_new_string("Result")); + plist_dict_set_item(dict, "Number", plist_new_uint(result)); + res = send_plist(client, tag, dict); + plist_free(dict); + } else { + /* binary packet */ + res = output_buffer_add_message(client, tag, MESSAGE_RESULT, &result, sizeof(uint32_t)); + } + return res; +} + +int client_notify_connect(struct mux_client *client, enum usbmuxd_result result) +{ + usbmuxd_log(LL_SPEW, "client_notify_connect fd %d result %d", client->fd, result); + if(client->state == CLIENT_DEAD) + return -1; + if(client->state != CLIENT_CONNECTING1) { + usbmuxd_log(LL_ERROR, "client_notify_connect when client %d is not in CONNECTING1 state", client->fd); + return -1; + } + if(send_result(client, client->connect_tag, result) < 0) + return -1; + if(result == RESULT_OK) { + client->state = CLIENT_CONNECTING2; + client->events = POLLOUT; // wait for the result packet to go through + // no longer need this + free(client->ib_buf); + client->ib_buf = NULL; + } else { + client->state = CLIENT_COMMAND; + } + return 0; +} + +static plist_t create_device_attached_plist(struct device_info *dev) +{ + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "MessageType", plist_new_string("Attached")); + plist_dict_set_item(dict, "DeviceID", plist_new_uint(dev->id)); + plist_t props = plist_new_dict(); + plist_dict_set_item(props, "ConnectionSpeed", plist_new_uint(dev->speed)); + plist_dict_set_item(props, "ConnectionType", plist_new_string("USB")); + plist_dict_set_item(props, "DeviceID", plist_new_uint(dev->id)); + plist_dict_set_item(props, "LocationID", plist_new_uint(dev->location)); + plist_dict_set_item(props, "ProductID", plist_new_uint(dev->pid)); + plist_dict_set_item(props, "SerialNumber", plist_new_string(dev->serial)); + plist_dict_set_item(dict, "Properties", props); + return dict; +} + +static int send_device_list(struct mux_client *client, uint32_t tag) +{ + int res = -1; + plist_t dict = plist_new_dict(); + plist_t devices = plist_new_array(); + + struct device_info *devs = NULL; + struct device_info *dev; + int i; + + int count = device_get_list(0, &devs); + dev = devs; + for (i = 0; devs && i < count; i++) { + plist_t device = create_device_attached_plist(dev++); + if (device) { + plist_array_append_item(devices, device); + } + } + if (devs) + free(devs); + + plist_dict_set_item(dict, "DeviceList", devices); + res = send_plist(client, tag, dict); + plist_free(dict); + return res; +} + +static int send_listener_list(struct mux_client *client, uint32_t tag) +{ + int res = -1; + + plist_t dict = plist_new_dict(); + plist_t listeners = plist_new_array(); + + mutex_lock(&client_list_mutex); + FOREACH(struct mux_client *lc, &client_list) { + if (lc->state == CLIENT_LISTEN) { + plist_t n = NULL; + plist_t l = plist_new_dict(); + plist_dict_set_item(l, "Blacklisted", plist_new_bool(0)); + n = NULL; + if (lc->info) { + n = plist_dict_get_item(lc->info, "BundleID"); + } + if (n) { + plist_dict_set_item(l, "BundleID", plist_copy(n)); + } + plist_dict_set_item(l, "ConnType", plist_new_uint(0)); + + n = NULL; + char *progname = NULL; + if (lc->info) { + n = plist_dict_get_item(lc->info, "ProgName"); + } + if (n) { + plist_get_string_val(n, &progname); + } + if (!progname) { + progname = strdup("unknown"); + } + char *idstring = malloc(strlen(progname) + 12); + sprintf(idstring, "%u-%s", client->number, progname); + + plist_dict_set_item(l, "ID String", plist_new_string(idstring)); + free(idstring); + plist_dict_set_item(l, "ProgName", plist_new_string(progname)); + free(progname); + + n = NULL; + uint64_t version = 0; + if (lc->info) { + n = plist_dict_get_item(lc->info, "kLibUSBMuxVersion"); + } + if (n) { + plist_get_uint_val(n, &version); + } + plist_dict_set_item(l, "kLibUSBMuxVersion", plist_new_uint(version)); + + plist_array_append_item(listeners, l); + } + } ENDFOREACH + mutex_unlock(&client_list_mutex); + + plist_dict_set_item(dict, "ListenerList", listeners); + res = send_plist(client, tag, dict); + plist_free(dict); + + return res; +} + +static int send_system_buid(struct mux_client *client, uint32_t tag) +{ + int res = -1; + char* buid = NULL; + + config_get_system_buid(&buid); + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "BUID", plist_new_string(buid)); + free(buid); + res = send_plist(client, tag, dict); + plist_free(dict); + return res; +} + +static int send_pair_record(struct mux_client *client, uint32_t tag, const char* record_id) +{ + int res = -1; + char* record_data = NULL; + uint64_t record_size = 0; + + if (!record_id) { + return send_result(client, tag, EINVAL); + } + + config_get_device_record(record_id, &record_data, &record_size); + + if (record_data) { + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "PairRecordData", plist_new_data(record_data, record_size)); + free(record_data); + res = send_plist(client, tag, dict); + plist_free(dict); + } else { + res = send_result(client, tag, ENOENT); + } + return res; +} + +static int send_device_add(struct mux_client *client, struct device_info *dev) +{ + int res = -1; + if (client->proto_version == 1) { + /* XML plist packet */ + plist_t dict = create_device_attached_plist(dev); + res = send_plist(client, 0, dict); + plist_free(dict); + } else { + /* binary packet */ + struct usbmuxd_device_record dmsg; + memset(&dmsg, 0, sizeof(dmsg)); + dmsg.device_id = dev->id; + strncpy(dmsg.serial_number, dev->serial, 256); + dmsg.serial_number[255] = 0; + dmsg.location = dev->location; + dmsg.product_id = dev->pid; + res = output_buffer_add_message(client, 0, MESSAGE_DEVICE_ADD, &dmsg, sizeof(dmsg)); + } + return res; +} + +static int send_device_remove(struct mux_client *client, uint32_t device_id) +{ + int res = -1; + if (client->proto_version == 1) { + /* XML plist packet */ + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "MessageType", plist_new_string("Detached")); + plist_dict_set_item(dict, "DeviceID", plist_new_uint(device_id)); + res = send_plist(client, 0, dict); + plist_free(dict); + } else { + /* binary packet */ + res = output_buffer_add_message(client, 0, MESSAGE_DEVICE_REMOVE, &device_id, sizeof(uint32_t)); + } + return res; +} + +static int send_device_paired(struct mux_client *client, uint32_t device_id) +{ + int res = -1; + if (client->proto_version == 1) { + /* XML plist packet */ + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "MessageType", plist_new_string("Paired")); + plist_dict_set_item(dict, "DeviceID", plist_new_uint(device_id)); + res = send_plist(client, 0, dict); + plist_free(dict); + } + else { + /* binary packet */ + res = output_buffer_add_message(client, 0, MESSAGE_DEVICE_PAIRED, &device_id, sizeof(uint32_t)); + } + return res; +} + +static int start_listen(struct mux_client *client) +{ + struct device_info *devs = NULL; + struct device_info *dev; + int count, i; + + client->state = CLIENT_LISTEN; + + count = device_get_list(0, &devs); + dev = devs; + for(i=0; devs && i < count; i++) { + if(send_device_add(client, dev++) < 0) { + free(devs); + return -1; + } + } + if (devs) + free(devs); + + return count; +} + +static char* plist_dict_get_string_val(plist_t dict, const char* key) +{ + if (!dict || plist_get_node_type(dict) != PLIST_DICT) + return NULL; + plist_t item = plist_dict_get_item(dict, key); + if (!item || plist_get_node_type(item) != PLIST_STRING) + return NULL; + char *str = NULL; + plist_get_string_val(item, &str); + return str; +} + +static void update_client_info(struct mux_client *client, plist_t dict) +{ + plist_t node = NULL; + plist_t info = plist_new_dict(); + + node = plist_dict_get_item(dict, "BundleID"); + if (node && (plist_get_node_type(node) == PLIST_STRING)) { + plist_dict_set_item(info, "BundleID", plist_copy(node)); + } + + node = plist_dict_get_item(dict, "ClientVersionString"); + if (node && (plist_get_node_type(node) == PLIST_STRING)) { + plist_dict_set_item(info, "ClientVersionString", plist_copy(node)); + } + + node = plist_dict_get_item(dict, "ProgName"); + if (node && (plist_get_node_type(node) == PLIST_STRING)) { + plist_dict_set_item(info, "ProgName", plist_copy(node)); + } + + node = plist_dict_get_item(dict, "kLibUSBMuxVersion"); + if (node && (plist_get_node_type(node) == PLIST_UINT)) { + plist_dict_set_item(info, "kLibUSBMuxVersion", plist_copy(node)); + } + plist_free(client->info); + client->info = info; +} + +static int handle_command(struct mux_client *client, struct usbmuxd_header *hdr) +{ + int res; + usbmuxd_log(LL_DEBUG, "Client %d command len %d ver %d msg %d tag %d", client->fd, hdr->length, hdr->version, hdr->message, hdr->tag); + + if(client->state != CLIENT_COMMAND) { + usbmuxd_log(LL_ERROR, "Client %d command received in the wrong state, got %d but want %d", client->fd, client->state, CLIENT_COMMAND); + if(send_result(client, hdr->tag, RESULT_BADCOMMAND) < 0) + return -1; + client_close(client); + return -1; + } + + if((hdr->version != 0) && (hdr->version != 1)) { + usbmuxd_log(LL_INFO, "Client %d version mismatch: expected 0 or 1, got %d", client->fd, hdr->version); + send_result(client, hdr->tag, RESULT_BADVERSION); + return 0; + } + + struct usbmuxd_connect_request *ch; + char *payload; + uint32_t payload_size; + + switch(hdr->message) { + case MESSAGE_PLIST: + client->proto_version = 1; + payload = (char*)(hdr) + sizeof(struct usbmuxd_header); + payload_size = hdr->length - sizeof(struct usbmuxd_header); + plist_t dict = NULL; + plist_from_xml(payload, payload_size, &dict); + if (!dict) { + usbmuxd_log(LL_ERROR, "Could not parse plist from payload!"); + return -1; + } else { + char *message = NULL; + plist_t node = plist_dict_get_item(dict, "MessageType"); + if (!node || plist_get_node_type(node) != PLIST_STRING) { + usbmuxd_log(LL_ERROR, "Could not read valid MessageType node from plist!"); + plist_free(dict); + return -1; + } + plist_get_string_val(node, &message); + if (!message) { + usbmuxd_log(LL_ERROR, "Could not extract MessageType from plist!"); + plist_free(dict); + return -1; + } + update_client_info(client, dict); + if (!strcmp(message, "Listen")) { + free(message); + plist_free(dict); + if (send_result(client, hdr->tag, 0) < 0) + return -1; + usbmuxd_log(LL_DEBUG, "Client %d now LISTENING", client->fd); + return start_listen(client); + } else if (!strcmp(message, "Connect")) { + uint64_t val; + uint16_t portnum = 0; + uint32_t device_id = 0; + free(message); + // get device id + node = plist_dict_get_item(dict, "DeviceID"); + if (!node) { + usbmuxd_log(LL_ERROR, "Received connect request without device_id!"); + plist_free(dict); + if (send_result(client, hdr->tag, RESULT_BADDEV) < 0) + return -1; + return 0; + } + val = 0; + plist_get_uint_val(node, &val); + device_id = (uint32_t)val; + + // get port number + node = plist_dict_get_item(dict, "PortNumber"); + if (!node) { + usbmuxd_log(LL_ERROR, "Received connect request without port number!"); + plist_free(dict); + if (send_result(client, hdr->tag, RESULT_BADCOMMAND) < 0) + return -1; + return 0; + } + val = 0; + plist_get_uint_val(node, &val); + portnum = (uint16_t)val; + plist_free(dict); + + usbmuxd_log(LL_DEBUG, "Client %d requesting connection to device %d port %d", client->fd, device_id, ntohs(portnum)); + res = device_start_connect(device_id, ntohs(portnum), client); + if(res < 0) { + if (send_result(client, hdr->tag, -res) < 0) + return -1; + } else { + client->connect_tag = hdr->tag; + client->connect_device = device_id; + client->state = CLIENT_CONNECTING1; + } + return 0; + } else if (!strcmp(message, "ListDevices")) { + free(message); + plist_free(dict); + if (send_device_list(client, hdr->tag) < 0) + return -1; + return 0; + } else if (!strcmp(message, "ListListeners")) { + free(message); + plist_free(dict); + if (send_listener_list(client, hdr->tag) < 0) + return -1; + return 0; + } else if (!strcmp(message, "ReadBUID")) { + free(message); + plist_free(dict); + if (send_system_buid(client, hdr->tag) < 0) + return -1; + return 0; + } else if (!strcmp(message, "ReadPairRecord")) { + free(message); + char* record_id = plist_dict_get_string_val(dict, "PairRecordID"); + plist_free(dict); + + res = send_pair_record(client, hdr->tag, record_id); + if (record_id) + free(record_id); + if (res < 0) + return -1; + return 0; + } else if (!strcmp(message, "SavePairRecord")) { + uint32_t rval = RESULT_OK; + free(message); + char* record_id = plist_dict_get_string_val(dict, "PairRecordID"); + char* record_data = NULL; + uint64_t record_size = 0; + plist_t rdata = plist_dict_get_item(dict, "PairRecordData"); + if (rdata && plist_get_node_type(rdata) == PLIST_DATA) { + plist_get_data_val(rdata, &record_data, &record_size); + } + + if (record_id && record_data) { + res = config_set_device_record(record_id, record_data, record_size); + if (res < 0) { + rval = -res; + } else { + plist_t p_dev_id = plist_dict_get_item(dict, "DeviceID"); + uint32_t dev_id = 0; + if (p_dev_id && plist_get_node_type(p_dev_id) == PLIST_UINT) { + uint64_t u_dev_id = 0; + plist_get_uint_val(p_dev_id, &u_dev_id); + dev_id = (uint32_t)u_dev_id; + } + if (dev_id > 0) { + struct device_info *devs = NULL; + struct device_info *dev; + int i; + int count = device_get_list(1, &devs); + int found = 0; + dev = devs; + for (i = 0; devs && i < count; i++, dev++) { + if ((uint32_t)dev->id == dev_id && (strcmp(dev->serial, record_id) == 0)) { + found++; + break; + } + } + if (!found) { + usbmuxd_log(LL_ERROR, "ERROR: SavePairRecord: DeviceID %d (%s) is not connected\n", dev_id, record_id); + } else { + client_device_paired(dev_id); + } + free(devs); + } + } + free(record_id); + } else { + rval = EINVAL; + } + free(record_data); + plist_free(dict); + if (send_result(client, hdr->tag, rval) < 0) + return -1; + return 0; + } else if (!strcmp(message, "DeletePairRecord")) { + uint32_t rval = RESULT_OK; + free(message); + char* record_id = plist_dict_get_string_val(dict, "PairRecordID"); + plist_free(dict); + if (record_id) { + res = config_remove_device_record(record_id); + if (res < 0) { + rval = -res; + } + free(record_id); + } else { + rval = EINVAL; + } + if (send_result(client, hdr->tag, rval) < 0) + return -1; + return 0; + } else { + usbmuxd_log(LL_ERROR, "Unexpected command '%s' received!", message); + free(message); + plist_free(dict); + if (send_result(client, hdr->tag, RESULT_BADCOMMAND) < 0) + return -1; + return 0; + } + } + // should not be reached?! + return -1; + case MESSAGE_LISTEN: + if(send_result(client, hdr->tag, 0) < 0) + return -1; + usbmuxd_log(LL_DEBUG, "Client %d now LISTENING", client->fd); + return start_listen(client); + case MESSAGE_CONNECT: + ch = (void*)hdr; + usbmuxd_log(LL_DEBUG, "Client %d connection request to device %d port %d", client->fd, ch->device_id, ntohs(ch->port)); + res = device_start_connect(ch->device_id, ntohs(ch->port), client); + if(res < 0) { + if(send_result(client, hdr->tag, -res) < 0) + return -1; + } else { + client->connect_tag = hdr->tag; + client->connect_device = ch->device_id; + client->state = CLIENT_CONNECTING1; + } + return 0; + default: + usbmuxd_log(LL_ERROR, "Client %d invalid command %d", client->fd, hdr->message); + if(send_result(client, hdr->tag, RESULT_BADCOMMAND) < 0) + return -1; + return 0; + } + return -1; +} + +static void output_buffer_process(struct mux_client *client) +{ + int res; + if(!client->ob_size) { + usbmuxd_log(LL_WARNING, "Client %d OUT process but nothing to send?", client->fd); + client->events &= ~POLLOUT; + return; + } + res = send(client->fd, client->ob_buf, client->ob_size, 0); + if(res <= 0) { + usbmuxd_log(LL_ERROR, "Sending to client fd %d failed: %d %s", client->fd, res, strerror(errno)); + client_close(client); + return; + } + if((uint32_t)res == client->ob_size) { + client->ob_size = 0; + client->events &= ~POLLOUT; + if(client->state == CLIENT_CONNECTING2) { + usbmuxd_log(LL_DEBUG, "Client %d switching to CONNECTED state", client->fd); + client->state = CLIENT_CONNECTED; + client->events = client->devents; + // no longer need this + free(client->ob_buf); + client->ob_buf = NULL; + } + } else { + client->ob_size -= res; + memmove(client->ob_buf, client->ob_buf + res, client->ob_size); + } +} +static void input_buffer_process(struct mux_client *client) +{ + int res; + int did_read = 0; + if(client->ib_size < sizeof(struct usbmuxd_header)) { + res = recv(client->fd, client->ib_buf + client->ib_size, sizeof(struct usbmuxd_header) - client->ib_size, 0); + if(res <= 0) { + if(res < 0) + usbmuxd_log(LL_ERROR, "Receive from client fd %d failed: %s", client->fd, strerror(errno)); + else + usbmuxd_log(LL_INFO, "Client %d connection closed", client->fd); + client_close(client); + return; + } + client->ib_size += res; + if(client->ib_size < sizeof(struct usbmuxd_header)) + return; + did_read = 1; + } + struct usbmuxd_header *hdr = (void*)client->ib_buf; + if(hdr->length > client->ib_capacity) { + usbmuxd_log(LL_INFO, "Client %d message is too long (%d bytes)", client->fd, hdr->length); + client_close(client); + return; + } + if(hdr->length < sizeof(struct usbmuxd_header)) { + usbmuxd_log(LL_ERROR, "Client %d message is too short (%d bytes)", client->fd, hdr->length); + client_close(client); + return; + } + if(client->ib_size < hdr->length) { + if(did_read) + return; //maybe we would block, so defer to next loop + res = recv(client->fd, client->ib_buf + client->ib_size, hdr->length - client->ib_size, 0); + if(res < 0) { + usbmuxd_log(LL_ERROR, "Receive from client fd %d failed: %s", client->fd, strerror(errno)); + client_close(client); + return; + } else if(res == 0) { + usbmuxd_log(LL_INFO, "Client %d connection closed", client->fd); + client_close(client); + return; + } + client->ib_size += res; + if(client->ib_size < hdr->length) + return; + } + handle_command(client, hdr); + client->ib_size = 0; +} + +void client_process(int fd, short events) +{ + struct mux_client *client = NULL; + mutex_lock(&client_list_mutex); + FOREACH(struct mux_client *lc, &client_list) { + if(lc->fd == fd) { + client = lc; + break; + } + } ENDFOREACH + mutex_unlock(&client_list_mutex); + + if(!client) { + usbmuxd_log(LL_INFO, "client_process: fd %d not found in client list", fd); + return; + } + + if(client->state == CLIENT_CONNECTED) { + usbmuxd_log(LL_SPEW, "client_process in CONNECTED state"); + device_client_process(client->connect_device, client, events); + } else { + if(events & POLLIN) { + input_buffer_process(client); + } else if(events & POLLOUT) { //not both in case client died as part of process_recv + output_buffer_process(client); + } + } + +} + +void client_device_add(struct device_info *dev) +{ + mutex_lock(&client_list_mutex); + usbmuxd_log(LL_DEBUG, "client_device_add: id %d, location 0x%x, serial %s", dev->id, dev->location, dev->serial); + device_set_visible(dev->id); + FOREACH(struct mux_client *client, &client_list) { + if(client->state == CLIENT_LISTEN) + send_device_add(client, dev); + } ENDFOREACH + mutex_unlock(&client_list_mutex); +} + +void client_device_remove(int device_id) +{ + mutex_lock(&client_list_mutex); + uint32_t id = device_id; + usbmuxd_log(LL_DEBUG, "client_device_remove: id %d", device_id); + FOREACH(struct mux_client *client, &client_list) { + if(client->state == CLIENT_LISTEN) + send_device_remove(client, id); + } ENDFOREACH + mutex_unlock(&client_list_mutex); +} + +void client_device_paired(int device_id) +{ + mutex_lock(&client_list_mutex); + uint32_t id = device_id; + usbmuxd_log(LL_DEBUG, "client_device_paired: id %d", device_id); + FOREACH(struct mux_client *client, &client_list) { + if (client->state == CLIENT_LISTEN) + send_device_paired(client, id); + } ENDFOREACH + mutex_unlock(&client_list_mutex); +} + +void client_init(void) +{ + usbmuxd_log(LL_DEBUG, "client_init"); + collection_init(&client_list); + mutex_init(&client_list_mutex); +} + +void client_shutdown(void) +{ + usbmuxd_log(LL_DEBUG, "client_shutdown"); + FOREACH(struct mux_client *client, &client_list) { + client_close(client); + } ENDFOREACH + mutex_destroy(&client_list_mutex); + collection_free(&client_list); +} |