#include #include #include #include #include #include #include #include #include #include #include #include "common/packet.h" /** * This is a proof of concept uinput daemon. * Mainly what we've learnt here is that linux interfaces aren't really ABI * compatible around different architectures, which is dissapointing as it means * all the interfaces basically need to be wrapped around in network transparent containers * which is a boring work, especially when the interfaces are not small. It also means underlying * data now needs to be understood and we can't act as simply proxy, which means if interfaces * are changed the wrappers need to be changed as well. * * The host and client will understand each other if stars are aligned right: * - Host and client have same native word size. * - Host and client were compiled with similar compiler that pads structures identically, * even if architectures differ. */ struct core { int uinput; }; static int _ioctl(const int fd, const unsigned long request, void *arg) { int ret; do { ret = ioctl(fd, request, arg); } while (ret == -1 && (errno == EINTR || errno == EAGAIN)); return ret; } #define ioctl(...) error "Use _ioctl instead" static void fd_close_or_exit(const int fd) { if (fd >= 0 && close(fd) != 0) err(EXIT_FAILURE, "close"); } static void core_ioctl(const struct core *core, const struct ioctl *ioctl, const size_t bytes, void *data) { const uint32_t dir[] = { _IOC_NONE, // IOCTL_NONE _IOC_READ, // IOCTL_IOR _IOC_WRITE, // IOCTL_IOW, _IOC_READ | _IOC_WRITE, // IOCTL_IOWR }; if (ioctl->dir > IOCTL_IOWR) return; const unsigned long request = _IOC(dir[ioctl->dir], ioctl->type, ioctl->nr, bytes); if (_ioctl(core->uinput, request, data) == -1) err(EXIT_FAILURE, "ioctl"); } static void core_write(const struct core *core, const size_t bytes, const uint8_t *data) { if (write(core->uinput, data, bytes) != bytes) err(EXIT_FAILURE, "write: failed to write %zu bytes", bytes); } static void core_process_ioctl_variant(struct core *core, const struct ioctl *ioctl, FILE *src) { switch (ioctl->arg) { case IOCTL_ARG_OTHER: { uint64_t bytes; uint8_t arg[16*1000]; // ioctls have max 16kB arg size if ((bytes = fread(arg, 1, ioctl->bytes, src)) != ioctl->bytes) err(EXIT_FAILURE, "fread: ioctl failed to read %zu bytes, got %zu bytes", ioctl->bytes, bytes); core_ioctl(core, ioctl, ioctl->bytes, arg); } break; case IOCTL_ARG_INT: { int arg = ioctl->integer; core_ioctl(core, ioctl, sizeof(arg), (void*)(intptr_t)arg); } break; } } static void core_process(struct core *core, const struct packet *packet, FILE *src) { assert(core && packet); switch (packet->type) { case PACKET_IOCTL: core_process_ioctl_variant(core, &packet->ioctl, src); break; case PACKET_WRITE: { uint64_t bytes; uint8_t arg[16*1000]; if ((bytes = fread(arg, 1, packet->write.bytes, src)) != packet->write.bytes) errx(EXIT_FAILURE, "fread: write failed to read %zu bytes, got %zu bytes", packet->write.bytes, bytes); core_write(core, packet->write.bytes, arg); } break; } } static void core_release(struct core *core) { if (!core) return; _ioctl(core->uinput, UI_DEV_DESTROY, NULL); fd_close_or_exit(core->uinput); *core = (struct core){0}; } static void core_init(struct core *core) { assert(core); if ((core->uinput = open("/dev/uinput", O_WRONLY | O_CLOEXEC)) < 0) err(EXIT_FAILURE, "open(%s)", "/dev/uinput"); } static void core_on_exit(const int signal, void *core) { assert(core); warnx("core_on_exit (%d)", signal); core_release(core); } static void info(void) { const char *remote = getenv("TCPREMOTEIP"); warnx("version %s", UINPUTD_VERSION); warnx("remote ip: %s", (remote ? remote : "none")); warnx("sizeof(struct packet) is %zu bytes", sizeof(struct packet)); warnx("sizeof(struct input_event) is %zu bytes", sizeof(struct input_event)); warnx("sizeof(struct uinput_user_dev) is %zu bytes", sizeof(struct uinput_user_dev)); } static void sigterm(const int signal) { warnx("%s", (signal == SIGTERM ? "SIGTERM" : "SIGINT")); exit(EXIT_SUCCESS); } int main(int argc, const char *argv[]) { { const struct sigaction action = { .sa_handler = sigterm, }; if (sigaction(SIGTERM, &action, NULL) != 0 || sigaction(SIGINT, &action, NULL) != 0) err(EXIT_FAILURE, "sigaction"); } info(); struct core core = {0}; on_exit(core_on_exit, &core); core_init(&core); struct packet packet; while (true) { size_t bytes; if ((bytes = fread(&packet, 1, sizeof(packet), stdin)) != sizeof(packet)) errx(EXIT_FAILURE, "invalid packet size %zu, expected %zu", bytes, sizeof(packet)); core_process(&core, &packet, stdin); } /** * The core will be destroyed when main returns, thus we exit(); * instead to avoid cleanup functions from causing SIGSEGV. */ exit(EXIT_SUCCESS); return EXIT_SUCCESS; }