diff options
Diffstat (limited to 'libusbmuxd')
-rw-r--r-- | libusbmuxd/CMakeLists.txt | 12 | ||||
-rw-r--r-- | libusbmuxd/libusbmuxd.c | 248 | ||||
-rw-r--r-- | libusbmuxd/sock_stuff.c | 301 | ||||
-rw-r--r-- | libusbmuxd/sock_stuff.h | 28 | ||||
-rw-r--r-- | libusbmuxd/usbmuxd-proto.h | 52 | ||||
-rw-r--r-- | libusbmuxd/usbmuxd.h | 91 |
6 files changed, 732 insertions, 0 deletions
diff --git a/libusbmuxd/CMakeLists.txt b/libusbmuxd/CMakeLists.txt new file mode 100644 index 0000000..61de1a8 --- /dev/null +++ b/libusbmuxd/CMakeLists.txt @@ -0,0 +1,12 @@ +add_library (libusbmuxd libusbmuxd.c sock_stuff.c) + +# 'lib' is a UNIXism, the proper CMake target is usbmuxd +# But we can't use that due to the conflict with the usbmuxd daemon, +# so instead change the library output base name to usbmuxd here +set_target_properties(libusbmuxd PROPERTIES OUTPUT_NAME usbmuxd) + +install(TARGETS libusbmuxd + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib +) +install(FILES usbmuxd.h usbmuxd-proto.h DESTINATION include) diff --git a/libusbmuxd/libusbmuxd.c b/libusbmuxd/libusbmuxd.c new file mode 100644 index 0000000..090695f --- /dev/null +++ b/libusbmuxd/libusbmuxd.c @@ -0,0 +1,248 @@ +#include <stdint.h> +#include <stdlib.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <unistd.h> + +// usbmuxd public interface +#include "usbmuxd.h" +// usbmuxd protocol +#include "usbmuxd-proto.h" +// socket utility functions +#include "sock_stuff.h" + +static int usbmuxd_get_result(int sfd, uint32_t tag, uint32_t * result) +{ + struct usbmuxd_result res; + int recv_len; + + if (!result) { + return -EINVAL; + } + + if ((recv_len = recv_buf(sfd, &res, sizeof(res))) <= 0) { + perror("recv"); + return -errno; + } else { + if ((recv_len == sizeof(res)) + && (res.header.length == (uint32_t) recv_len) + && (res.header.reserved == 0) + && (res.header.type == USBMUXD_RESULT) + ) { + *result = res.result; + if (res.header.tag == tag) { + return 1; + } else { + return 0; + } + } + } + + return -1; +} + +int usbmuxd_scan(usbmuxd_scan_result ** available_devices) +{ + struct usbmuxd_scan_request s_req; + int sfd; + int scan_success = 0; + uint32_t res; + uint32_t pktlen; + int recv_len; + usbmuxd_scan_result *newlist = NULL; + struct usbmuxd_device_info_record dev_info_pkt; + int dev_cnt = 0; + + sfd = connect_unix_socket(USBMUXD_SOCKET_FILE); + if (sfd < 0) { + fprintf(stderr, "%s: error opening socket!\n", __func__); + return sfd; + } + + s_req.header.length = sizeof(struct usbmuxd_scan_request); + s_req.header.reserved = 0; + s_req.header.type = USBMUXD_SCAN; + s_req.header.tag = 2; + + // send scan request packet + if (send_buf(sfd, &s_req, s_req.header.length) == + (int) s_req.header.length) { + res = -1; + // get response + if (usbmuxd_get_result(sfd, s_req.header.tag, &res) && (res == 0)) { + scan_success = 1; + } else { + fprintf(stderr, + "%s: Did not get response to scan request (with result=0)...\n", + __func__); + close(sfd); + return res; + } + } + + if (!scan_success) { + fprintf(stderr, "%s: Could not send scan request!\n", __func__); + return -1; + } + + *available_devices = NULL; + // receive device list + while (1) { + if (recv_buf_timeout(sfd, &pktlen, 4, MSG_PEEK, 1000) == 4) { + if (pktlen != sizeof(dev_info_pkt)) { + // invalid packet size received! + fprintf(stderr, + "%s: Invalid packet size (%d) received when expecting a device info record.\n", + __func__, pktlen); + break; + } + + recv_len = recv_buf(sfd, &dev_info_pkt, pktlen); + if (recv_len <= 0) { + fprintf(stderr, + "%s: Error when receiving device info record\n", + __func__); + break; + } else if ((uint32_t) recv_len < pktlen) { + fprintf(stderr, + "%s: received less data than specified in header!\n", + __func__); + } else { + //fprintf(stderr, "%s: got device record with id %d, UUID=%s\n", __func__, dev_info_pkt.device_info.device_id, dev_info_pkt.device_info.serial_number); + newlist = + (usbmuxd_scan_result *) realloc(*available_devices, + sizeof + (usbmuxd_scan_result) * + (dev_cnt + 1)); + if (newlist) { + newlist[dev_cnt].handle = + (int) dev_info_pkt.device.device_id; + newlist[dev_cnt].product_id = + dev_info_pkt.device.product_id; + memset(newlist[dev_cnt].serial_number, '\0', + sizeof(newlist[dev_cnt].serial_number)); + memcpy(newlist[dev_cnt].serial_number, + dev_info_pkt.device.serial_number, + sizeof(dev_info_pkt.device.serial_number)); + *available_devices = newlist; + dev_cnt++; + } else { + fprintf(stderr, + "%s: ERROR: out of memory when trying to realloc!\n", + __func__); + break; + } + } + } else { + // we _should_ have all of them now. + // or perhaps an error occured. + break; + } + } + + // terminating zero record + newlist = + (usbmuxd_scan_result *) realloc(*available_devices, + sizeof(usbmuxd_scan_result) * + (dev_cnt + 1)); + memset(newlist + dev_cnt, 0, sizeof(usbmuxd_scan_result)); + *available_devices = newlist; + + return dev_cnt; +} + +int usbmuxd_connect(const int handle, const unsigned short tcp_port) +{ + int sfd; + struct usbmuxd_connect_request c_req; + int connected = 0; + uint32_t res = -1; + + sfd = connect_unix_socket(USBMUXD_SOCKET_FILE); + if (sfd < 0) { + fprintf(stderr, "%s: Error: Connection to usbmuxd failed: %s\n", + __func__, strerror(errno)); + return sfd; + } + + c_req.header.length = sizeof(c_req); + c_req.header.reserved = 0; + c_req.header.type = USBMUXD_CONNECT; + c_req.header.tag = 3; + c_req.device_id = (uint32_t) handle; + c_req.tcp_dport = htons(tcp_port); + c_req.reserved = 0; + + if (send_buf(sfd, &c_req, sizeof(c_req)) < 0) { + perror("send"); + } else { + // read ACK + //fprintf(stderr, "%s: Reading connect result...\n", __func__); + if (usbmuxd_get_result(sfd, c_req.header.tag, &res)) { + if (res == 0) { + //fprintf(stderr, "%s: Connect success!\n", __func__); + connected = 1; + } else { + fprintf(stderr, "%s: Connect failed, Error code=%d\n", + __func__, res); + } + } + } + + if (connected) { + return sfd; + } + + close(sfd); + + return -1; +} + +int usbmuxd_disconnect(int sfd) +{ + return close(sfd); +} + +int usbmuxd_send(int sfd, const char *data, uint32_t len, uint32_t *sent_bytes) +{ + int num_sent; + + if (sfd < 0) { + return -EINVAL; + } + + num_sent = send(sfd, (void*)data, len, 0); + if (num_sent < 0) { + *sent_bytes = 0; + fprintf(stderr, "%s: Error %d when sending: %s\n", __func__, num_sent, strerror(errno)); + return num_sent; + } else if ((uint32_t)num_sent < len) { + fprintf(stderr, "%s: Warning: Did not send enough (only %d of %d)\n", __func__, num_sent, len); + } + + *sent_bytes = num_sent; + + return 0; +} + +int usbmuxd_recv_timeout(int sfd, char *data, uint32_t len, uint32_t *recv_bytes, unsigned int timeout) +{ + int num_recv = recv_buf_timeout(sfd, (void*)data, len, 0, timeout); + if (num_recv < 0) { + *recv_bytes = 0; + return num_recv; + } + + *recv_bytes = num_recv; + + return 0; +} + +int usbmuxd_recv(int sfd, char *data, uint32_t len, uint32_t *recv_bytes) +{ + return usbmuxd_recv_timeout(sfd, data, len, recv_bytes, 5000); +} + diff --git a/libusbmuxd/sock_stuff.c b/libusbmuxd/sock_stuff.c new file mode 100644 index 0000000..137375d --- /dev/null +++ b/libusbmuxd/sock_stuff.c @@ -0,0 +1,301 @@ +#include <stdio.h> +#include <stddef.h> +#include <unistd.h> +#include <errno.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <netdb.h> +#include <arpa/inet.h> +#include "sock_stuff.h" + +#define RECV_TIMEOUT 20000 + +static int verbose = 0; + +void sock_stuff_set_verbose(int level) +{ + verbose = level; +} + +int create_unix_socket(const char *filename) +{ + struct sockaddr_un name; + int sock; + size_t size; + + // remove if still present + unlink(filename); + + /* Create the socket. */ + sock = socket(PF_LOCAL, SOCK_STREAM, 0); + if (sock < 0) { + perror("socket"); + return -1; + } + + /* Bind a name to the socket. */ + name.sun_family = AF_LOCAL; + strncpy(name.sun_path, filename, sizeof(name.sun_path)); + name.sun_path[sizeof(name.sun_path) - 1] = '\0'; + + /* The size of the address is + the offset of the start of the filename, + plus its length, + plus one for the terminating null byte. + Alternatively you can just do: + size = SUN_LEN (&name); + */ + size = (offsetof(struct sockaddr_un, sun_path) + + strlen(name.sun_path) + 1); + + if (bind(sock, (struct sockaddr *) &name, size) < 0) { + perror("bind"); + close(sock); + return -1; + } + + if (listen(sock, 10) < 0) { + perror("listen"); + close(sock); + return -1; + } + + return sock; +} + +int connect_unix_socket(const char *filename) +{ + struct sockaddr_un name; + int sfd = -1; + size_t size; + struct stat fst; + + // check if socket file exists... + if (stat(filename, &fst) != 0) { + if (verbose >= 2) + fprintf(stderr, "%s: stat '%s': %s\n", __func__, filename, + strerror(errno)); + return -1; + } + // ... and if it is a unix domain socket + if (!S_ISSOCK(fst.st_mode)) { + if (verbose >= 2) + fprintf(stderr, "%s: File '%s' is not a socket!\n", __func__, + filename); + return -1; + } + // make a new socket + if ((sfd = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) { + if (verbose >= 2) + fprintf(stderr, "%s: socket: %s\n", __func__, strerror(errno)); + return -1; + } + // and connect to 'filename' + name.sun_family = AF_LOCAL; + strncpy(name.sun_path, filename, sizeof(name.sun_path)); + name.sun_path[sizeof(name.sun_path) - 1] = 0; + + size = (offsetof(struct sockaddr_un, sun_path) + + strlen(name.sun_path) + 1); + + if (connect(sfd, (struct sockaddr *) &name, size) < 0) { + close(sfd); + if (verbose >= 2) + fprintf(stderr, "%s: connect: %s\n", __func__, + strerror(errno)); + return -1; + } + + return sfd; +} + +int create_socket(uint16_t port) +{ + int sfd = -1; + int yes = 1; + struct sockaddr_in saddr; + + if (0 > (sfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))) { + perror("socket()"); + return -1; + } + + if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { + perror("setsockopt()"); + close(sfd); + return -1; + } + + memset((void *) &saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = htonl(INADDR_ANY); + saddr.sin_port = htons(port); + + if (0 > bind(sfd, (struct sockaddr *) &saddr, sizeof(saddr))) { + perror("bind()"); + close(sfd); + return -1; + } + + if (listen(sfd, 1) == -1) { + perror("listen()"); + close(sfd); + return -1; + } + + return sfd; +} + +int connect_socket(const char *addr, uint16_t port) +{ + int sfd = -1; + int yes = 1; + struct hostent *hp; + struct sockaddr_in saddr; + + if (!addr) { + errno = EINVAL; + return -1; + } + + if ((hp = gethostbyname(addr)) == NULL) { + if (verbose >= 2) + fprintf(stderr, "%s: unknown host '%s'\n", __func__, addr); + return -1; + } + + if (!hp->h_addr) { + if (verbose >= 2) + fprintf(stderr, "%s: gethostbyname returned NULL address!\n", + __func__); + return -1; + } + + if (0 > (sfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))) { + perror("socket()"); + return -1; + } + + if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { + perror("setsockopt()"); + close(sfd); + return -1; + } + + memset((void *) &saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = *(uint32_t *) hp->h_addr; + saddr.sin_port = htons(port); + + if (connect(sfd, (struct sockaddr *) &saddr, sizeof(saddr)) < 0) { + perror("connect"); + close(sfd); + return -2; + } + + return sfd; +} + +int check_fd(int fd, fd_mode fdm, unsigned int timeout) +{ + fd_set fds; + int sret; + int eagain; + struct timeval to; + + if (fd <= 0) { + if (verbose >= 2) + fprintf(stderr, "ERROR: invalid fd in check_fd %d\n", fd); + return -1; + } + + FD_ZERO(&fds); + FD_SET(fd, &fds); + + to.tv_sec = (time_t) (timeout / 1000); + to.tv_usec = (time_t) ((timeout - (to.tv_sec * 1000)) * 1000); + + sret = -1; + + do { + eagain = 0; + switch (fdm) { + case FD_READ: + sret = select(fd + 1, &fds, NULL, NULL, &to); + break; + case FD_WRITE: + sret = select(fd + 1, NULL, &fds, NULL, &to); + break; + case FD_EXCEPT: + sret = select(fd + 1, NULL, NULL, &fds, &to); + break; + default: + return -1; + } + + if (sret < 0) { + switch (errno) { + case EINTR: + // interrupt signal in select + if (verbose >= 2) + fprintf(stderr, "%s: EINTR\n", __func__); + eagain = 1; + break; + case EAGAIN: + if (verbose >= 2) + fprintf(stderr, "%s: EAGAIN\n", __func__); + break; + default: + if (verbose >= 2) + fprintf(stderr, "%s: select failed: %s\n", __func__, + strerror(errno)); + return -1; + } + } + } while (eagain); + + return sret; +} + +int recv_buf(int fd, void *data, size_t length) +{ + return recv_buf_timeout(fd, data, length, 0, RECV_TIMEOUT); +} + +int peek_buf(int fd, void *data, size_t length) +{ + return recv_buf_timeout(fd, data, length, MSG_PEEK, RECV_TIMEOUT); +} + +int recv_buf_timeout(int fd, void *data, size_t length, int flags, + unsigned int timeout) +{ + int res; + int result; + + // check if data is available + res = check_fd(fd, FD_READ, timeout); + if (res <= 0) { + return res; + } + // if we get here, there _is_ data available + result = recv(fd, data, length, flags); + if (res > 0 && result == 0) { + // but this is an error condition + if (verbose >= 3) + fprintf(stderr, "%s: fd=%d recv returned 0\n", __func__, fd); + return -EAGAIN; + } + if (result < 0) { + return -errno; + } + return result; +} + +int send_buf(int fd, void *data, size_t length) +{ + return send(fd, data, length, 0); +} diff --git a/libusbmuxd/sock_stuff.h b/libusbmuxd/sock_stuff.h new file mode 100644 index 0000000..190f7e1 --- /dev/null +++ b/libusbmuxd/sock_stuff.h @@ -0,0 +1,28 @@ +#ifndef __SOCK_STUFF_H +#define __SOCK_STUFF_H + +#include <stdint.h> + +enum fd_mode { + FD_READ, + FD_WRITE, + FD_EXCEPT +}; +typedef enum fd_mode fd_mode; + +int create_unix_socket(const char *filename); +int connect_unix_socket(const char *filename); +int create_socket(uint16_t port); +int connect_socket(const char *addr, uint16_t port); +int check_fd(int fd, fd_mode fdm, unsigned int timeout); + +int recv_buf(int fd, void *data, size_t size); +int peek_buf(int fd, void *data, size_t size); +int recv_buf_timeout(int fd, void *data, size_t size, int flags, + unsigned int timeout); + +int send_buf(int fd, void *data, size_t size); + +void sock_stuff_set_verbose(int level); + +#endif /* __SOCK_STUFF_H */ diff --git a/libusbmuxd/usbmuxd-proto.h b/libusbmuxd/usbmuxd-proto.h new file mode 100644 index 0000000..7f8c2d6 --- /dev/null +++ b/libusbmuxd/usbmuxd-proto.h @@ -0,0 +1,52 @@ +/* Protocol defintion for usbmuxd proxy protocol */ + +#ifndef __USBMUXD_PROTO_H +#define __USBMUXD_PROTO_H + +#include <stdint.h> + +#define USBMUXD_SOCKET_FILE "/var/run/usbmuxd" + +struct usbmuxd_header { + uint32_t length; // length of message, including header + uint32_t reserved; // always zero + uint32_t type; // message type + uint32_t tag; // responses to this query will echo back this tag +} __attribute__((__packed__)); + +struct usbmuxd_result { + struct usbmuxd_header header; + uint32_t result; +} __attribute__((__packed__)); + +struct usbmuxd_connect_request { + struct usbmuxd_header header; + uint32_t device_id; + uint16_t tcp_dport; // TCP port number + uint16_t reserved; // set to zero +} __attribute__((__packed__)); + +struct usbmuxd_device { + uint32_t device_id; + uint16_t product_id; + char serial_number[40]; +} __attribute__((__packed__)); + +struct usbmuxd_device_info_record { + struct usbmuxd_header header; + struct usbmuxd_device device; + char padding[222]; +} __attribute__((__packed__)); + +struct usbmuxd_scan_request { + struct usbmuxd_header header; +} __attribute__((__packed__)); + +enum { + USBMUXD_RESULT = 1, + USBMUXD_CONNECT = 2, + USBMUXD_SCAN = 3, + USBMUXD_DEVICE_INFO = 4, +}; + +#endif /* __USBMUXD_PROTO_H */ diff --git a/libusbmuxd/usbmuxd.h b/libusbmuxd/usbmuxd.h new file mode 100644 index 0000000..ba45ec3 --- /dev/null +++ b/libusbmuxd/usbmuxd.h @@ -0,0 +1,91 @@ +#ifndef __USBMUXD_H +#define __USBMUXD_H + +/** + * Array entry returned by 'usbmuxd_scan()' scanning. + * + * If more than one device is available, 'product_id' and + * 'serial_number' and be analysed to help make a selection. + * The relevant 'handle' should be passed to 'usbmuxd_connect()', to + * start a proxy connection. The value 'handle' should be considered + * opaque and no presumption made about the meaning of its value. + */ +typedef struct { + int handle; + int product_id; + char serial_number[41]; +} usbmuxd_scan_result; + +/** + * Contacts usbmuxd and performs a scan for connected devices. + * + * @param available_devices pointer to array of usbmuxd_scan_result. + * Array of available devices. The required 'handle' + * should be passed to 'usbmuxd_connect()'. The returned array + * is zero-terminated for convenience; the final (unused) + * entry containing handle == 0. The returned array pointer + * should be freed by passing to 'free()' after use. + * + * @return number of available devices, zero on no devices, or negative on error + */ +int usbmuxd_scan(usbmuxd_scan_result **available_devices); + +/** + * Request proxy connect to + * + * @param handle returned by 'usbmuxd_scan()' + * + * @param tcp_port TCP port number on device, in range 0-65535. + * common values are 62078 for lockdown, and 22 for SSH. + * + * @return file descriptor socket of the connection, or -1 on error + */ +int usbmuxd_connect(const int handle, const unsigned short tcp_port); + +/** + * Disconnect. For now, this just closes the socket file descriptor. + * + * @param sfd socker file descriptor returned by usbmuxd_connect() + * + * @return 0 on success, -1 on error. + */ +int usbmuxd_disconnect(int sfd); + +/** + * Send data to the specified socket. + * + * @param sfd socket file descriptor returned by usbmuxd_connect() + * @param data buffer to send + * @param len size of buffer to send + * @param sent_bytes how many bytes sent + * + * @return 0 on success, a negative errno value otherwise. + */ +int usbmuxd_send(int sfd, const char *data, uint32_t len, uint32_t *sent_bytes); + +/** + * Receive data from the specified socket. + * + * @param sfd socket file descriptor returned by usbmuxd_connect() + * @param data buffer to put the data to + * @param len number of bytes to receive + * @param recv_bytes number of bytes received + * @param timeout how many milliseconds to wait for data + * + * @return 0 on success, a negative errno value otherwise. + */ +int usbmuxd_recv_timeout(int sfd, char *data, uint32_t len, uint32_t *recv_bytes, unsigned int timeout); + +/** + * Receive data from the specified socket with a default timeout. + * + * @param sfd socket file descriptor returned by usbmuxd_connect() + * @param data buffer to put the data to + * @param len number of bytes to receive + * @param recv_bytes number of bytes received + * + * @return 0 on success, a negative errno value otherwise. + */ +int usbmuxd_recv(int sfd, char *data, uint32_t len, uint32_t *recv_bytes); + +#endif /* __USBMUXD_H */ |