diff options
Diffstat (limited to '')
-rw-r--r-- | ptee.c | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/ptee.c b/ptee.c new file mode 100644 index 0000000..0c93ab0 --- /dev/null +++ b/ptee.c @@ -0,0 +1,155 @@ +/* Copyright (C) 2018 Causal Agent June <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <err.h> +#include <errno.h> +#include <poll.h> +#include <pwd.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sysexits.h> +#include <termios.h> +#include <unistd.h> + +#if defined __FreeBSD__ +#include <libutil.h> +#elif defined __linux__ +#include <pty.h> +#else +#include <util.h> +#endif + +extern int winch(void); + +static struct { + int pty; + int winch; + int input; + int local; + int remote; +} fd = { -1, -1, STDIN_FILENO, STDERR_FILENO, STDOUT_FILENO }; + +static struct termios saveTerm; +static void restoreTerm(void) { + tcsetattr(fd.local, TCSADRAIN, &saveTerm); +} + +int main(int argc, char *argv[]) { + int error; + + if (isatty(fd.remote)) { + errx(EX_USAGE, "stdout is not redirected"); + } + + if (argc > 1) { + argv++; + } else { + uid_t uid = getuid(); + struct passwd *user = getpwuid(uid); + if (!user) err(EX_OSFILE, "/etc/passwd"); + argv[0] = user->pw_shell; + } + + error = tcgetattr(fd.local, &saveTerm); + if (error) err(EX_IOERR, "tcgetattr"); + atexit(restoreTerm); + + struct termios raw; + cfmakeraw(&raw); + error = tcsetattr(fd.local, TCSADRAIN, &raw); + if (error) err(EX_IOERR, "tcsetattr"); + + struct winsize window; + error = ioctl(fd.local, TIOCGWINSZ, &window); + if (error) err(EX_IOERR, "TIOCGWINSZ"); + fd.winch = winch(); + + pid_t pid = forkpty(&fd.pty, NULL, NULL, &window); + if (pid < 0) err(EX_OSERR, "forkpty"); + + if (!pid) { + execvp(*argv, argv); + err(EX_NOINPUT, "%s", *argv); + } + + char buf[4096]; + ssize_t totalSize = 0; + struct pollfd fds[3] = { + { .fd = fd.input, .events = POLLIN }, + { .fd = fd.pty, .events = POLLIN }, + { .fd = fd.winch, .events = POLLIN }, + }; + for (;;) { + int nfds = poll(fds, 3, -1); + if (nfds < 0) { + if (errno == EINTR) continue; + err(EX_IOERR, "poll"); + } + + if (fds[0].revents == POLLIN) { + ssize_t readSize = read(fd.input, buf, sizeof(buf)); + if (readSize < 0) err(EX_IOERR, "read(%d)", fd.input); + + ssize_t writeSize = write(fd.pty, buf, readSize); + if (writeSize < 0) err(EX_IOERR, "write(%d)", fd.pty); + if (writeSize < readSize) errx(EX_IOERR, "short write(%d)", fd.pty); + } + + if (fds[1].revents == POLLIN) { + ssize_t readSize = read(fd.pty, buf, sizeof(buf)); + if (readSize < 0) err(EX_IOERR, "read(%d)", fd.pty); + + ssize_t writeSize = write(fd.local, buf, readSize); + if (writeSize < 0) err(EX_IOERR, "write(%d)", fd.local); + if (writeSize < readSize) err(EX_IOERR, "short write(%d)", fd.local); + + writeSize = write(fd.remote, buf, readSize); + if (writeSize < 0) err(EX_IOERR, "write(%d)", fd.remote); + if (writeSize < readSize) err(EX_IOERR, "short write(%d)", fd.remote); + + if ((totalSize += readSize) >= 1024 * 1024) { + struct winsize original = window; + window.ws_row = 1; + window.ws_col = 1; + + int error = ioctl(fd.pty, TIOCSWINSZ, &window); + if (error) err(EX_IOERR, "TIOCSWINSZ"); + + window = original; + error = ioctl(fd.pty, TIOCSWINSZ, &window); + if (error) err(EX_IOERR, "TIOCWINSZ"); + + totalSize = 0; + } + } + + if (fds[2].revents == POLLIN) { + ssize_t readSize = read(fd.winch, &window, sizeof(window)); + if (readSize < 0) err(EX_IOERR, "read(%d)", fd.winch); + if ((size_t)readSize < sizeof(window)) { + errx(EX_IOERR, "short read(%d)", fd.winch); + } + + int error = ioctl(fd.pty, TIOCSWINSZ, &window); + if (error) err(EX_IOERR, "TIOCSWINSZ"); + } + + int status; + pid_t dead = waitpid(pid, &status, WNOHANG); + if (dead < 0) err(EX_OSERR, "waitpid(%d)", pid); + if (dead) return WIFEXITED(status) ? WEXITSTATUS(status) : EX_SOFTWARE; + } +} |