#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* TTY data */ typedef struct tty_t { char *path; int fd; int pid; struct tty_t *next; } tty_t; /* Local Data */ static int running; static int epoll; static int sigs; static tty_t *ttys; /* Helper functions */ void error(const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "Error "); vfprintf(stderr, fmt, ap); fprintf(stderr, ": %s\n", strerror(errno)); va_end(ap); if (!running) exit(1); } int add_poll(int fd, void *ptr) { struct epoll_event ctl = { .events = EPOLLIN, .data.ptr = ptr, }; return epoll_ctl(epoll, EPOLL_CTL_ADD, fd, &ctl); } int del_poll(int fd) { return epoll_ctl(epoll, EPOLL_CTL_DEL, fd, NULL); } static int start_tty(tty_t *tty) { const char *prompt = "[Press enter to login]"; struct termios attr; if (chown(tty->path, 0, 0) < 0) return -1; if (chmod(tty->path, 0600) < 0) return -1; if ((tty->fd = open(tty->path, O_RDWR|O_NOCTTY|O_NONBLOCK|O_CLOEXEC, 0)) < 0) return -1; if (add_poll(tty->fd, tty) < 0) return -1; tcgetattr(tty->fd, &attr); attr.c_lflag &= ~ECHO; tcsetattr(tty->fd, TCSANOW, &attr); write(tty->fd, "\033c", 2); write(tty->fd, "\033[?1c", 5); write(tty->fd, prompt, strlen(prompt)); return 0; } static void read_tty(tty_t *tty) { int flags; char ch, login; while (read(tty->fd, &ch, 1) == 1) if (ch == '\n' || ch == '\r') login = 1; if (!login) return; if ((tty->pid = fork()) < 0) return; if (tty->pid == 0) { if (putenv("TERM=linux")) error("setting environment"); if (setsid() < 0) error("setting sid"); if (write(tty->fd, "\033c", 2) < 0) error("resetting tty"); if ((flags = fcntl(tty->fd, F_GETFL)) < 0) error("getting fd flags"); if (fcntl(tty->fd, F_SETFL, flags&~O_NONBLOCK) < 0) error("setting blocking flags"); if (ioctl(tty->fd, TIOCSCTTY, 0) < 0) error("setting ctty"); if (dup2(tty->fd, 0) != 0) error("setting stdin"); if (dup2(tty->fd, 1) != 1) error("setting stdout"); if (dup2(tty->fd, 2) != 2) error("setting stderr"); if (execl("/bin/login", "login", NULL) < 0) error("execing login program"); } del_poll(tty->fd); close(tty->fd); } void on_child(void) { struct signalfd_siginfo info; if (read(sigs, &info, sizeof(info)) != sizeof(info)) return; if (info.ssi_signo != SIGCHLD) return; int status; pid_t pid; tty_t *tty; while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { for (tty = ttys; tty; tty = tty->next) { if (pid == tty->pid) { tty->pid = 0; start_tty(tty); } } } } /* Main */ int main(int argc, char **argv) { int i, count; tty_t *tty; sigset_t mask; /* Check arguments */ if (argc <= 1) { printf("usage: gettyd ...\n"); return 0; } /* Setup */ sigemptyset(&mask); sigaddset(&mask, SIGCHLD); sigaddset(&mask, SIGHUP); if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) error("blocking signals"); if ((sigs = signalfd(-1, &mask, SFD_CLOEXEC)) < 0) error("creating signal fd"); if ((epoll = epoll_create1(EPOLL_CLOEXEC)) < 0) error("creating epoll"); if (add_poll(sigs, &sigs) < 0) error("adding signal epoll"); /* Open TTYs */ for (i = 1; i < argc; i++) { if (!(tty = malloc(sizeof(tty_t)))) error("allocating memory"); if (asprintf(&tty->path, "/dev/%s", argv[i]) < 0) error("allocating path name"); if (start_tty(tty) < 0) error("starting tty '%s'", tty->path); tty->next = ttys; ttys = tty; } /* Main loop */ running = 1; while (1) { struct epoll_event event; errno = 0; count = epoll_wait(epoll, &event, 1, -1); if (errno == EINTR) continue; if (count < 0) continue; if (event.data.ptr == &sigs) on_child(); else read_tty(event.data.ptr); } return 0; }