blob: adcb1f1ed498251df38903b57c05b9a917475120 [file] [log] [blame]
/*
* inetcat.c -- simple netcat-like tool that enables service access to iOS devices
*
* Copyright (C) 2017 Adrien Guinet <adrien@guinet.me>
*
* Based on iproxy which is based upon iTunnel source code, Copyright (c)
* 2008 Jing Su. http://www.cs.toronto.edu/~jingsu/itunnel/
*
* 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 of the License, or
* (at your option) any later version.
*
* 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
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <stddef.h>
#include <unistd.h>
#include <errno.h>
#include <getopt.h>
#ifdef WIN32
#include <windows.h>
#include <winsock2.h>
#else
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/ioctl.h>
#include <signal.h>
#endif
#include "usbmuxd.h"
#include "socket.h"
static int debug_level = 0;
static size_t read_data_socket(int fd, uint8_t* buf, size_t bufsize)
{
#ifdef WIN32
u_long bytesavailable = 0;
if (fd == STDIN_FILENO) {
bytesavailable = bufsize;
} else if (ioctlsocket(fd, FIONREAD, &bytesavailable) != 0) {
perror("ioctlsocket FIONREAD failed");
exit(1);
}
#else
size_t bytesavailable = 0;
if (ioctl(fd, FIONREAD, &bytesavailable) != 0) {
perror("ioctl FIONREAD failed");
exit(1);
}
#endif
size_t bufread = (bytesavailable >= bufsize) ? bufsize:bytesavailable;
ssize_t ret = read(fd, buf, bufread);
if (ret < 0) {
perror("read failed");
exit(1);
}
return (size_t)ret;
}
static void print_usage(int argc, char **argv, int is_error)
{
char *name = NULL;
name = strrchr(argv[0], '/');
fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] DEVICE_PORT\n\n", (name ? name + 1: argv[0]));
fprintf(is_error ? stderr : stdout,
"Opens a read/write interface via STDIN/STDOUT to a TCP port on a usbmux device.\n\n" \
" -u, --udid UDID target specific device by UDID\n" \
" -n, --network connect to network device\n" \
" -l, --local connect to USB device (default)\n" \
" -h, --help prints usage information\n" \
" -d, --debug increase debug level\n" \
"\n" \
"Homepage: <" PACKAGE_URL ">\n"
"Bug reports: <" PACKAGE_BUGREPORT ">\n"
"\n"
);
}
int main(int argc, char **argv)
{
const struct option longopts[] = {
{ "debug", no_argument, NULL, 'd' },
{ "help", no_argument, NULL, 'h' },
{ "udid", required_argument, NULL, 'u' },
{ "local", no_argument, NULL, 'l' },
{ "network", no_argument, NULL, 'n' },
{ NULL, 0, NULL, 0}
};
char* device_udid = NULL;
static enum usbmux_lookup_options lookup_opts = 0;
int c = 0;
while ((c = getopt_long(argc, argv, "dhu:ln", longopts, NULL)) != -1) {
switch (c) {
case 'd':
libusbmuxd_set_debug_level(++debug_level);
break;
case 'u':
if (!*optarg) {
fprintf(stderr, "ERROR: UDID must not be empty!\n");
print_usage(argc, argv, 1);
return 2;
}
free(device_udid);
device_udid = strdup(optarg);
break;
case 'l':
lookup_opts |= DEVICE_LOOKUP_USBMUX;
break;
case 'n':
lookup_opts |= DEVICE_LOOKUP_NETWORK;
break;
case 'h':
print_usage(argc, argv, 0);
return 0;
default:
print_usage(argc, argv, 1);
return 2;
}
}
if (lookup_opts == 0) {
lookup_opts = DEVICE_LOOKUP_USBMUX;
}
argc -= optind;
argv += optind;
if (argc < 1) {
print_usage(argc + optind, argv - optind, 1);
return 2;
}
int device_port = atoi(argv[0]);
if (!device_port) {
fprintf(stderr, "Invalid device_port specified!\n");
return -EINVAL;
}
#ifndef WIN32
signal(SIGPIPE, SIG_IGN);
#endif
usbmuxd_device_info_t *dev_list = NULL;
usbmuxd_device_info_t *dev = NULL;
usbmuxd_device_info_t muxdev;
if (device_udid) {
if (usbmuxd_get_device(device_udid, &muxdev, lookup_opts) > 0) {
dev = &muxdev;
}
} else {
int count;
if ((count = usbmuxd_get_device_list(&dev_list)) < 0) {
printf("Connecting to usbmuxd failed, terminating.\n");
free(dev_list);
return 1;
}
if (dev_list == NULL || dev_list[0].handle == 0) {
fprintf(stderr, "No connected device found, terminating.\n");
free(dev_list);
return 1;
}
int i;
for (i = 0; i < count; i++) {
if (dev_list[i].conn_type == CONNECTION_TYPE_USB && (lookup_opts & DEVICE_LOOKUP_USBMUX)) {
dev = &(dev_list[i]);
break;
} else if (dev_list[i].conn_type == CONNECTION_TYPE_NETWORK && (lookup_opts & DEVICE_LOOKUP_NETWORK)) {
dev = &(dev_list[i]);
break;
}
}
}
if (dev == NULL || dev->handle == 0) {
fprintf(stderr, "No connected/matching device found, disconnecting client.\n");
free(dev_list);
return 1;
}
int devfd = -1;
if (dev->conn_type == CONNECTION_TYPE_NETWORK) {
unsigned char saddr_[32];
memset(saddr_, '\0', sizeof(saddr_));
struct sockaddr* saddr = (struct sockaddr*)&saddr_[0];
if (((char*)dev->conn_data)[1] == 0x02) { // AF_INET
saddr->sa_family = AF_INET;
memcpy(&saddr->sa_data[0], (char*)dev->conn_data+2, 14);
}
else if (((char*)dev->conn_data)[1] == 0x1E) { //AF_INET6 (bsd)
#ifdef AF_INET6
saddr->sa_family = AF_INET6;
memcpy(&saddr->sa_data[0], (char*)dev->conn_data+2, 26);
#else
fprintf(stderr, "ERROR: Got an IPv6 address but this system doesn't support IPv6\n");
free(dev_list);
return 1;
#endif
}
else {
fprintf(stderr, "Unsupported address family 0x%02x\n", ((char*)dev->conn_data)[1]);
free(dev_list);
return 1;
}
char addrtxt[48];
addrtxt[0] = '\0';
if (!socket_addr_to_string(saddr, addrtxt, sizeof(addrtxt))) {
fprintf(stderr, "Failed to convert network address: %d (%s)\n", errno, strerror(errno));
}
devfd = socket_connect_addr(saddr, device_port);
} else if (dev->conn_type == CONNECTION_TYPE_USB) {
devfd = usbmuxd_connect(dev->handle, device_port);
}
free(dev_list);
if (devfd < 0) {
fprintf(stderr, "Error connecting to device: %s\n", strerror(errno));
return 1;
}
fd_set fds;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
FD_SET(devfd, &fds);
int ret = 0;
uint8_t buf[4096];
while (1) {
fd_set read_fds = fds;
int ret_sel = select(devfd+1, &read_fds, NULL, NULL, NULL);
if (ret_sel < 0) {
perror("select");
ret = 1;
break;
}
if (FD_ISSET(STDIN_FILENO, &read_fds)) {
size_t n = read_data_socket(STDIN_FILENO, buf, sizeof(buf));
if (n == 0) {
break;
}
write(devfd, buf, n);
}
if (FD_ISSET(devfd, &read_fds)) {
size_t n = read_data_socket(devfd, buf, sizeof(buf));
if (n == 0) {
break;
}
write(STDOUT_FILENO, buf, n);
}
}
socket_close(devfd);
return ret;
}