vtmitm.c (4094B)
1#define _XOPEN_SOURCE 600 2 3#include <sys/stat.h> 4#include <sys/types.h> 5#include <sys/select.h> 6#include <sys/wait.h> 7#include <sys/ioctl.h> 8#include <fcntl.h> 9#include <unistd.h> 10#include <termios.h> 11#include <signal.h> 12#include <errno.h> 13#include <stdbool.h> 14#include <stdarg.h> 15#include <string.h> 16#include <stdio.h> 17#include <stdlib.h> 18 19struct termios oldterm; 20int master, slave; 21int pipeout; 22pid_t child; 23 24bool encode_resize; 25 26void 27usage(void) 28{ 29 fprintf(stderr, "Usage: vtmitm [--encode-resize] -- CMD [ARG]..\n"); 30 exit(1); 31} 32 33void 34err(const char *fmtstr, ...) 35{ 36 va_list ap; 37 38 va_start(ap, fmtstr); 39 vfprintf(stderr, fmtstr, ap); 40 fprintf(stderr, ": %s\r\n", strerror(errno)); 41 va_end(ap); 42 43 exit(1); 44} 45 46int 47writeall(int fd, char *buffer, size_t size) 48{ 49 ssize_t nbytes; 50 51 while (size) { 52 nbytes = write(fd, buffer, size); 53 if (nbytes <= 0) return 1; 54 buffer += nbytes; 55 size -= nbytes; 56 } 57 58 return 0; 59} 60 61int 62fwd(int in, int out, int out2) 63{ 64 static char buffer[8194]; 65 ssize_t got, i, last; 66 67 do { 68 got = read(in, buffer, sizeof(buffer)); 69 if (got < 0) return 1; 70 71 if (writeall(out, buffer, got)) 72 return 1; 73 74 if (out2 >= 0) { 75 if (writeall(out2, buffer, got)) 76 return 1; 77 } 78 } while (got == sizeof(buffer)); 79 80 return 0; 81} 82 83void 84mitm(int pts, int pipe) 85{ 86 fd_set fds; 87 88 while (1) { 89 FD_ZERO(&fds); 90 FD_SET(0, &fds); 91 FD_SET(pts, &fds); 92 93 if (select(pts + 1, &fds, 0, 0, 0) == -1) { 94 if (errno == EINTR) continue; 95 return; 96 } 97 98 if (FD_ISSET(0, &fds)) { /* stdin -> ptty */ 99 if (fwd(0, pts, -1)) return; 100 } 101 102 if (FD_ISSET(pts, &fds)) { /* ptty -> stdout + pipe */ 103 if (fwd(pts, 1, pipe)) return; 104 } 105 } 106} 107 108void 109rawterm(struct termios *term) 110{ 111 term->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP); 112 term->c_iflag &= ~(INLCR | IGNCR | ICRNL | IXON); 113 term->c_oflag &= ~OPOST; 114 term->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); 115 term->c_cflag &= ~(CSIZE | PARENB); 116 term->c_cflag |= CS8; 117} 118 119void 120sigwinch(int sig) 121{ 122 char buf[64]; 123 struct winsize wsize; 124 125 if (ioctl(0, TIOCGWINSZ, &wsize) != -1) 126 ioctl(slave, TIOCSWINSZ, &wsize); 127 kill(child, SIGWINCH); 128 signal(SIGWINCH, sigwinch); 129 130 /* transmit new size info in form of xterm resize escape sequence */ 131 snprintf(buf, sizeof(buf), "\x1b[8;%u;%ut", wsize.ws_row, wsize.ws_col); 132 write(pipeout, buf, sizeof(buf)); 133} 134 135void 136sigchld(int sig) 137{ 138 int exitcode; 139 140 wait(&exitcode); 141 exit(exitcode); 142} 143 144void 145cleanup(void) 146{ 147 tcsetattr(0, TCSANOW, &oldterm); 148} 149 150int 151main(int argc, char *const *argv) 152{ 153 struct termios newterm; 154 struct winsize wsize; 155 char *const *arg, *const *cmd; 156 int exitcode; 157 158 cmd = NULL; 159 encode_resize = false; 160 for (arg = argv + 1; *arg; arg++) { 161 if (!strcmp(*arg, "--encode-resize")) { 162 encode_resize = true; 163 } else if (!strcmp(*arg, "--")) { 164 cmd = arg + 1; 165 break; 166 } else { 167 usage(); 168 } 169 } 170 171 if (!cmd) usage(); 172 173 pipeout = dup(1); 174 if (!freopen("/dev/tty", "w", stdout)) 175 err("main: open /dev/tty"); 176 dup2(fileno(stdout), 1); 177 178 master = posix_openpt(O_RDWR); 179 if (master == -1) err("main: posix_openpt"); 180 if (grantpt(master)) err("main: grantpt"); 181 if (unlockpt(master)) err("main: unlockpt"); 182 183 slave = open(ptsname(master), O_RDWR); 184 if (slave == -1) err("main: open pts"); 185 186 signal(SIGCHLD, sigchld); 187 188 child = fork(); 189 if (child < 0) err("main: fork"); 190 191 if (child == 0) { 192 close(master); 193 194 signal(SIGCHLD, SIG_DFL); 195 196 if (setsid() == -1) 197 err("slave: setsid"); 198 199 if (ioctl(slave, TIOCSCTTY, 0) == -1) 200 err("slave: ioctl TIOCSCTTY"); 201 202 if (dup2(slave, 0) == -1) 203 err("slave: dup2 stdin"); 204 if (dup2(slave, 1) == -1) 205 err("slave: dup2 stdout"); 206 if (dup2(slave, 2) == -1) 207 err("slave: dup2 stderr"); 208 close(slave); 209 210 execvp(*cmd, cmd); 211 err("slave: execvp"); 212 } else { 213 if (encode_resize) { 214 signal(SIGWINCH, sigwinch); 215 raise(SIGWINCH); 216 } 217 218 if (tcgetattr(0, &oldterm)) 219 err("master: tcgetattr"); 220 newterm = oldterm; 221 rawterm(&newterm); 222 if (tcsetattr(0, TCSANOW, &newterm)) 223 err("master: tcsetattr"); 224 atexit(cleanup); 225 226 mitm(master, pipeout); 227 228 waitpid(child, &exitcode, 0); 229 close(master); 230 } 231 232 return exitcode; 233}