#include #include #include #include #include #include #include #include static int write_all(int fd, const void *data, size_t length) { const unsigned char *cursor = data; while (length > 0) { ssize_t written = write(fd, cursor, length); if (written < 0 && errno == EINTR) continue; if (written <= 0) return -1; cursor += written; length -= (size_t)written; } return 0; } static int connect_ipv4(const char *host, const char *port_string) { char *end = NULL; long port = strtol(port_string, &end, 10); struct sockaddr_in address; int fd; if (end == port_string || *end != '\0' || port <= 0 || port > 65535) { errno = EINVAL; return -1; } memset(&address, 0, sizeof(address)); address.sin_family = AF_INET; address.sin_port = htons((unsigned short)port); if (inet_pton(AF_INET, host, &address.sin_addr) != 1) { errno = EINVAL; return -1; } fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) return -1; if (connect(fd, (struct sockaddr *)&address, sizeof(address)) < 0) { int saved_errno = errno; close(fd); errno = saved_errno; return -1; } return fd; } static int proxy_stdio(int socket_fd) { int input_open = 1; unsigned char buffer[16384]; for (;;) { struct pollfd poll_fds[2] = { { .fd = STDIN_FILENO, .events = input_open ? POLLIN : 0 }, { .fd = socket_fd, .events = POLLIN }, }; int ready; do { ready = poll(poll_fds, 2, -1); } while (ready < 0 && errno == EINTR); if (ready < 0) return -1; if (input_open && (poll_fds[0].revents & (POLLIN | POLLHUP))) { ssize_t length = read(STDIN_FILENO, buffer, sizeof(buffer)); if (length < 0 && errno == EINTR) continue; if (length < 0) return -1; if (length == 0) { input_open = 0; if (shutdown(socket_fd, SHUT_WR) < 0 && errno != ENOTCONN) return -1; } else if (write_all(socket_fd, buffer, (size_t)length) < 0) { return -1; } } if (poll_fds[1].revents & (POLLIN | POLLHUP)) { ssize_t length = read(socket_fd, buffer, sizeof(buffer)); if (length < 0 && errno == EINTR) continue; if (length < 0) return -1; if (length == 0) return 0; if (write_all(STDOUT_FILENO, buffer, (size_t)length) < 0) return -1; } if (poll_fds[0].revents & (POLLERR | POLLNVAL)) { errno = EIO; return -1; } if (poll_fds[1].revents & (POLLERR | POLLNVAL)) { errno = EIO; return -1; } } } static int proxy_fdpass(int socket_fd) { char marker = 'F'; struct iovec iov = { .iov_base = &marker, .iov_len = 1 }; union { struct cmsghdr align; unsigned char bytes[CMSG_SPACE(sizeof(int))]; } control; struct msghdr message; struct cmsghdr *header; memset(&message, 0, sizeof(message)); memset(&control, 0, sizeof(control)); message.msg_iov = &iov; message.msg_iovlen = 1; message.msg_control = control.bytes; message.msg_controllen = sizeof(control.bytes); header = CMSG_FIRSTHDR(&message); header->cmsg_level = SOL_SOCKET; header->cmsg_type = SCM_RIGHTS; header->cmsg_len = CMSG_LEN(sizeof(int)); memcpy(CMSG_DATA(header), &socket_fd, sizeof(socket_fd)); return sendmsg(STDOUT_FILENO, &message, 0) == 1 ? 0 : -1; } int main(int argc, char **argv) { int socket_fd; int result; if (argc != 4 || (strcmp(argv[1], "stdio") != 0 && strcmp(argv[1], "fdpass") != 0)) { fprintf(stderr, "usage: ssh_proxy_helper {stdio|fdpass} HOST PORT\n"); return 2; } socket_fd = connect_ipv4(argv[2], argv[3]); if (socket_fd < 0) { perror("ssh_proxy_helper connect"); return 1; } result = strcmp(argv[1], "fdpass") == 0 ? proxy_fdpass(socket_fd) : proxy_stdio(socket_fd); if (result < 0) perror("ssh_proxy_helper"); close(socket_fd); return result == 0 ? 0 : 1; }