/* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include //#include "reptyr.h" #ifndef __linux__ #error reptyr is currently Linux-only. #endif static int verbose = 0; void _debug(const char *pfx, const char *msg, va_list ap) { if (pfx) fprintf(stderr, "%s", pfx); vfprintf(stderr, msg, ap); fprintf(stderr, "\n"); } void die(const char *msg, ...) { va_list ap; va_start(ap, msg); _debug("[!] ", msg, ap); va_end(ap); exit(1); } void debug(const char *msg, ...) { va_list ap; if (!verbose) return; va_start(ap, msg); _debug("[+] ", msg, ap); va_end(ap); } void error(const char *msg, ...) { va_list ap; va_start(ap, msg); _debug("[-] ", msg, ap); va_end(ap); } void setup_raw(struct termios *save) { struct termios set; if (tcgetattr(0, save) < 0) die("Unable to read terminal attributes: %m"); set = *save; cfmakeraw(&set); if (tcsetattr(0, TCSANOW, &set) < 0) die("Unable to set terminal attributes: %m"); } void resize_pty(int pty) { struct winsize sz; if (ioctl(0, TIOCGWINSZ, &sz) < 0) return; ioctl(pty, TIOCSWINSZ, &sz); } int writeall(int fd, const void *buf, ssize_t count) { ssize_t rv; while (count > 0) { rv = write(fd, buf, count); if (rv < 0) { if (errno == EINTR) continue; return rv; } count -= rv; buf += rv; } return 0; } volatile sig_atomic_t winch_happened = 0; void do_winch(int signal) { winch_happened = 1; } void do_proxy(int pty) { char buf[4096]; ssize_t count; fd_set set; while (1) { if (winch_happened) { winch_happened = 0; /* * FIXME: If a signal comes in after this point but before * select(), the resize will be delayed until we get more * input. signalfd() is probably the cleanest solution. */ resize_pty(pty); } FD_ZERO(&set); FD_SET(0, &set); FD_SET(pty, &set); if (select(pty+1, &set, NULL, NULL, NULL) < 0) { if (errno == EINTR) continue; fprintf(stderr, "select: %m"); return; } if (FD_ISSET(0, &set)) { count = read(0, buf, sizeof buf); if (count < 0) return; writeall(pty, buf, count); } if (FD_ISSET(pty, &set)) { count = read(pty, buf, sizeof buf); if (count < 0) return; writeall(1, buf, count); } } } void usage(char *me, FILE* out) { if (out == NULL) out = stderr; fprintf(out, "Usage: %s [-s] PID\n", me); fprintf(out, " %s -l|-L [COMMAND [ARGS]]\n", me); fprintf(out, " -l Create a new pty pair and print the name of the slave.\n"); fprintf(out, " if there are command-line arguments after -l\n"); fprintf(out, " they are executed with REPTYR_PTY set to path of pty.\n"); fprintf(out, " -L Like '-l', but also redirect the child's stdio to the slave.\n"); fprintf(out, " -s Attach fds 0-2 on the target, even if it is not attached to a tty.\n"); fprintf(out, " -h Print this help message and exit.\n"); fprintf(out, " -v Print the version number and exit.\n"); fprintf(out, " -V Print verbose debug output.\n"); } void check_yama_ptrace_scope(void) { int fd = open("/proc/sys/kernel/yama/ptrace_scope", O_RDONLY); if (fd >= 0) { char buf[256]; int n; n = read(fd, buf, sizeof buf); close(fd); if (n > 0) { if (!atoi(buf)) { return; } } } else if (errno == ENOENT) return; fprintf(stderr, "The kernel denied permission while attaching. If your uid matches\n"); fprintf(stderr, "the target's, check the value of /proc/sys/kernel/yama/ptrace_scope.\n"); fprintf(stderr, "For more information, see /etc/sysctl.d/10-ptrace.conf\n"); } int main(int argc, char **argv) { struct termios saved_termios; struct sigaction act; int pty; int arg = 1; int do_attach = 1; int force_stdio = 0; int unattached_script_redirection = 0; if (argc < 2) { usage(argv[0], NULL); return 2; } else if (argc == 2 && !strcmp(argv[1], "--help")) { usage(argv[0], stdout); return 0; } if (argv[arg][0] == '-') { switch(argv[arg][1]) { case 'h': usage(argv[0], stdout); return 0; case 'l': do_attach = 0; break; case 'L': do_attach = 0; unattached_script_redirection = 1; break; case 's': arg++; force_stdio = 1; break; case 'v': printf("This is reptyr version %s.\n", REPTYR_VERSION); printf(" by Nelson Elhage \n"); printf("http://github.com/nelhage/reptyr/\n"); return 0; case 'V': arg++; verbose = 1; break; default: usage(argv[0], NULL); return 1; } } if (do_attach && arg >= argc) { fprintf(stderr, "%s: No pid specified to attach\n", argv[0]); usage(argv[0], NULL); return 1; } if ((pty = open("/dev/ptmx", O_RDWR|O_NOCTTY)) < 0) die("Unable to open /dev/ptmx: %m"); if (unlockpt(pty) < 0) die("Unable to unlockpt: %m"); if (grantpt(pty) < 0) die("Unable to grantpt: %m"); if (do_attach) { pid_t child = atoi(argv[arg]); int err; if ((err = attach_child(child, ptsname(pty), force_stdio))) { fprintf(stderr, "Unable to attach to pid %d: %s\n", child, strerror(err)); if (err == EPERM) { check_yama_ptrace_scope(); } return 1; } } else { printf("Opened a new pty: %s\n", ptsname(pty)); fflush(stdout); if (argc > 2) { if(!fork()) { setenv("REPTYR_PTY", ptsname(pty), 1); if (unattached_script_redirection) { int f; setpgid(0, getppid()); setsid(); f = open(ptsname(pty), O_RDONLY, 0); dup2(f, 0); close(f); f = open(ptsname(pty), O_WRONLY, 0); dup2(f, 1); dup2(f,2); close(f); } close(pty); execvp(argv[2], argv+2); exit(1); } } } setup_raw(&saved_termios); memset(&act, 0, sizeof act); act.sa_handler = do_winch; act.sa_flags = 0; sigaction(SIGWINCH, &act, NULL); resize_pty(pty); do_proxy(pty); tcsetattr(0, TCSANOW, &saved_termios); return 0; }