/* cotty 0.4c Simple command-line pseudo terminal manager: allows to run coprocesses talking to each other thru their tty and/or pty. Most useful to drive from scripts programs that want a tty, as in cotty -d -- pppd silent 192.168.0.1:192.168.0.2 \ -- ssh -t root@remote pppd This particular use has been obsoleted under Linux (but probably not under the various free BSDs and proprietary Unices), as it can be done without cotty with pppd pty 'ssh -t root@remote pppd' silent 192.168.0.1:192.168.0.2 Other uses of cotty remain, as called by fwprc, or by my lispm script. See the Firewall-Piercing mini-HOWTO. Copyright (c) 1998-2001 François-René Rideau DDa(.ng-Vu~ Ba^n Many thanks to master hacker Robert Ehrlich for his kind and insightful advice and code snippet. Thanks to Vladimir Geogjaev for his codeful suggestions. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2, or (at your option) any later version. Actually, you may also redistribute it under the bugroff license (at your option). Relevant URLs: http://fare.tunes.org/files/fwprc/ http://www.linuxdoc.org/HOWTO/mini/Firewall-Piercing.html http://www.geocities.com/SoHo/Cafe/5947/bugroff.html $Id: cotty.c,v 1.16 2001/10/01 16:12:01 fare Exp $ */ #define COTTY_VERSION "0.4c" /* INSTALLATION Here is a sample installation script. YMMV. gunzip < cotty-*.c.gz > cotty.c $EDITOR cotty.c # if you want to modify the configuration # see CONFIGURATION below gcc -Os -fomit-frame-pointer -Wall -W -s cotty.c -o cotty # if it fails to compile on your setup, # then edit the configuration and retry install -s -m 755 -o root -g root cotty /usr/local/bin/ if GNU install is not available, try the following instead: strip cotty cp cotty /usr/local/bin/ */ /* INVOCATION e.g. cotty -- chat -f stupid.questions -- chat -f stupid.answers (commands are run each in a tty with data from any one being sent to the other) or cotty ++ command using -- as separator ++ other command (same, allowing for use of -- in command) or cotty lAm3_SePaRaT0R stupid command lAm3_SePaRaT0R stupider command (actually, the first non-option is considered as the separator). (Option -n specifies the same "normal" behavior as lack of option). or cotty -c -- telnet remote.host -- sh -c 'exec minicom -o -p $COTTY_TTY' (second command is run on current terminal instead of the second pseudo tty) or cotty -1 command (only one command is run, on a terminal whose master is connected to cotty's current stdin and stdout) or cotty -p command (only one command is run, controlling a pseudo tty master, and the name of the slave tty being printed on stdout; cotty exits immediately afterwards. Mostly same as pty-redir.c) or cotty -d -- program on tty -- program on pty two commands are run, first one on the tty, the second one on the pty. (should have been the other way round, but who cares enough to break existing documentation? If someone wants to clean the issue, add a new option). or Use The Source, Luke! to see how it all works or see the fwprc program and the Firewall-Piercing mini-HOWTO Every command is forked with environment variable COTTY_COTTY being defined to be the other command's TTY and COTTY_TTY its own TTY. With option -c, the second command has to explicitly do something using COTTY_TTY. With option -1, only the name of the cotty-taken tty is given, in COTTY_TTY for the first command, and in COTTY_COTTY for the second command. Remark that commands are executed with options as given to cotty. They are NOT implicitly sent to a shell for interpretation, although you can explicitly use sh -c '... $COTTY_TTY ...' as a valid command to be spawned by cotty. In particular, putting whole commands inside quotes will NOT work. BAD: cotty -- "pppd 1.2.3.4:1.2.3.5" -- "ssh -t foo@bar pppd" GOOD, but overly complex: cotty -- sh -c "$LOCAL_PPPD" -- sh -c "$SSH_COMMAND" GOOD: cotty -- pppd 1.2.3.4:1.2.3.5 -- ssh -t foo@bar pppd Also, in a remote command, be sure that you specify the full path for any command that is not in the standard path, as ssh or rlogin won't setup your usual path for you. */ /* HISTORY 19980604 cotty 0.1 Initial release of a working version. pty scanning (thanks to Robert Ehrlich for initial code). select-based proxy (thanks to vntty authors for code snippet). stty_raw. user-defined separator. heavy debug mode. signal handling to kill the whole family of cotty-related processes. nullfds to prevent problems when some of fd=0,1,2 not open. 19980623 cotty 0.2 fork-based proxy (thanks to Robert Ehrlich for insightful suggestion). 19980718 cotty 0.2b 19980809 cotty 0.3 Source reformatted in linux kernel C style. Skeletal support for further portability to other platforms than linux. Most functions, including main(), split into smaller inline functions. Handling of tty opening failure when pty successfully open. Multiple running modes: -n -c -1 -d (thanks to Vladimir Geogjaev for suggestion, initial code, debugging). History added. version() and usage() added. 19990118 cotty 0.3a support for UNIX98 PTYs. 20000420 cotty 0.3b TCSADRAIN instead of TCSANOW 20001102 cotty 0.4 parse_command_line was missing support for -d direct mode. Fixed. stty_raw was incorrectly setting output speed to 0. Fixed. (normal pseudo-speed is 38400; 0 has special semantics.) A few comments and messages added while debugging. PTY_UNIX98 is now the default. -p will exit immediately after printing argument. try to find OPEN_MAX in linux/limits.h instead of limits.h 20001119 cotty 0.4a Half-assed modifications so as to get cotty to work on Slowaris 2.8. Most notably renamed linux-specific PTY_UNIX98 to PTY_UNIX98_LINUX, and implemented a (hopefully) standards-compliant PTY_UNIX98 mode. Discovered cfmakeraw under glibc, but there seems to be no working equivalent (even done manually) under ScumOS; thus, implementation of stty_raw on ScumOS 5.8 is still very unsatisfactory. Thanks to Erik van Oosten for feedback. 20010125 cotty 0.4b Port to FreeBSD 4.x thanks to Jeff Ito . 20010930 cotty 0.4c The FreeBSD port worked on OpenBSD 2.9 by just enabling the #ifdef. */ /* NOTES Though this program comes with no warranty whatsoever, it has been carefully designed and implemented from the beginning with a strong concern for robustness, with the hope that it be usable in a (possibly setuid root) IP connection script (see fwprc and the Firewall-Piercing mini-HOWTO for examples). So as to be as robust as possible, we strive to catch all possible errors or weird conditions. C just sucks at that. POSIX makes it worse. If you find a bug or weakness or security hole in the program, please do tell so we can fix it. */ /* ------>8------>8------>8------>8------>8------>8------>8------>8------ */ /* CONFIGURATION */ /* Choose what pty/tty opening/naming interface is to be used. */ #undef PTY_BSD_64 /* BSD naming for 64 ptys */ #undef PTY_BSD_256 /* linux-extended BSD naming for 256 ptys */ #undef PTY_UNIX98_LINUX /* UNIX98 ptys, using Linux internals, for old glibc */ #define PTY_UNIX98 /* UNIX98 ptys, the portable X/Open way */ /* Choose either one of the twos proxies */ #define USE_FORK #undef USE_SELECT /* When using USE_FORK, you can define a path for an external cat to exec, or #undef CAT_PATH to use an internal version. For some unknown reason, /bin/cat doesn't seem to work correctly for our purposes, so this feature is experimental. I'd very much appreciate explanations as to why it doesn't work... (most likely some buffering is done that shouldn't; maybe some stty would help), */ /*#define CAT_PATH "/bin/cat"*/ #define CAT_PATH "cat" #undef CAT_PATH /* Define if cotty should try to reset the pty and tty to canonical state */ /* If not, and the kernel doesn't either, it's up to your application. */ #define USE_STTY_RAW /* enable or disable debugging */ #define DEBUG #undef DEBUG /* ------>8------>8------>8------>8------>8------>8------>8------>8------ */ /* system-dependent include files and definitions */ /* If you're not running one of the supported systems below, you lose. But then, since this program is GPL, you gain the right to modify the program so it works on yer fav'rite platform, and send me the diff's! Supported systems: GNU/Linux FreeBSD, NetBSD, OpenBSD Solaris 2 (without termios resetting) */ #if defined(__linux__) /* Works on Linux with GLIBC 2. Not tested in other settings. */ #define _XOPEN_SOURCE #define _GNU_SOURCE #include #include #include #define LEAVE_PROCESS_GROUP() setpgrp() #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) /* Reported to work on FreeBSD 4.x and OpenBSD 2.9 */ #define PTY_BSD_64 #undef PTY_BSD_256 #undef PTY_UNIX98_LINUX #undef PTY_UNIX98 #define LEAVE_PROCESS_GROUP() setpgid(0,0) #elif defined(__sun__) && defined(__svr4__) /* Minimally tested on Solaris 2.8 (SunOS 5.8) */ #define NEED_CFMAKERAW /* This glibc function was not present */ #undef USE_STTY_RAW /* couldn't get it to work properly. */ #define LEAVE_PROCESS_GROUP() setpgrp() #else # error Unknown Operating System. # error You must modify the source right above this error to port cotty. #endif /**** NO LUSER-SERVICEABLE PARTS BELOW ****/ /* That said, if you're reading this, you're probably a hacker, not just a mere luser, so go ahead proudly, although you hopefully won't have to modify much anything. */ /* ------>8------>8------>8------>8------>8------>8------>8------>8------ */ /* system-independent include files and definitions */ /* if one of them isn't in your system, then it will have to be moved to system-dependent parts, and/or conditionally included depending on system-dependent flags. */ #include #include #include #include #include #include #include #include #include #include /* ------>8------>8------>8------>8------>8------>8------>8------>8------ */ #if defined(USE_SELECT) # define XXFORK(x) # define XXSELECT(x) x #elif defined(USE_FORK) # define XXFORK(x) x # define XXSELECT(x) #else # error Neither USE_FORK nor USE_SELECT defined!!! #endif #ifdef DEBUG #define DBG(x) x #else #define DBG(x) #endif #define MSG(x...) DBG(errprintf(x)) /* ------>8------>8------>8------>8------>8------>8------>8------>8------ */ #define errprintf(msg...) fprintf(stderr, msg) #define abort_with(code,msg...) do { \ errprintf(msg); \ exit(code); \ } while (0) /* ------>8------>8------>8------>8------>8------>8------>8------>8------ */ /* * PTY handling. * We ain't do no locking or grantpt(). * Maybe we should? That'll be for cotty 0.4! */ #if defined(PTY_BSD_64) || defined(PTY_BSD_256) #define PTY_BSD static char ttydev[] = "/dev/ptyLN"; /* 0123456789 */ #if defined(PTY_BSD_256) static char letter[] = "pqrstuvwxyzabcde"; #elif defined(PTY_BSD_64) static char letter[] = "pqrs"; #endif static char num [] = "0123456789abcdef"; #define LETTER 8 #define NUM 9 #define TYPE 5 #define TTYLEN 11 static inline int primitive_get_master_pty(int mode) { static int li = 0, ni = -1; int fd; MSG("pgmp %d %d\n",li,ni); ttydev[TYPE] = 'p'; while (letter[li] != 0) { ttydev[LETTER] = letter[li]; while (num[++ni] != 0) { ttydev[NUM] = num[ni]; MSG("trying %s...",ttydev); fd = open(ttydev, mode); if (fd >= 0) { MSG(" good!\n"); return fd; } MSG(" bad!\n"); } ni = -1; li++ ; } li = 0; return -1; } /* pfdp, tfdp, pfnp, tfnp are pointers to recipients for function results: pty fd, tty fd, pty name, tty name. The latter two must have space for TTYLEN characters */ static inline int get_pty_tty(int* pfdp, int* tfdp, char* pfnp, char* tfnp) { int pfd,tfd; MSG("gpt\n"); while ( (pfd = primitive_get_master_pty(O_RDWR)) != -1 ) { if (pfnp) { strcpy(pfnp,ttydev); } ttydev[TYPE] = 't' ; MSG("trying %s... ",ttydev); tfd = open(ttydev,O_RDWR); if (tfd != -1) { MSG("good!\n"); if (tfnp) { strcpy(tfnp,ttydev); } if (pfdp) { *pfdp = pfd; } if (tfdp) { *tfdp = tfd; } return 0; } else { MSG("bad!\n"); close(pfd); } } return -1; } #elif defined(PTY_UNIX98_LINUX) #define MAX_PTSNUM 9999 static char ptydev[] = "/dev/ptmx"; static char ttydev[] = "/dev/pts/XXXX"; /* 0123456789012*/ #define NUM 9 #define TTYLEN 14 /* pfdp, tfdp, pfnp, tfnp are pointers to recipients for function results: pty fd, tty fd, pty name, tty name. The latter two must have space for TTYLEN characters */ static inline int get_pty_tty(int* pfdp, int* tfdp, char* pfnp, char* tfnp) { int pfd,tfd; int ptsnum, res; static const int unlock = 0; const int mode = O_RDWR; MSG("gpt\n"); pfd = open(ptydev, mode); if (pfd == -1) { goto early_fail; } /* unlock the pty. Is this really necessary??? */ res = ioctl(pfd, TIOCSPTLCK, &unlock); if ( res == -1 ) { goto fail; } res = ioctl(pfd, TIOCGPTN, &ptsnum); if ( (res == -1) || (ptsnum < 0) || (ptsnum > MAX_PTSNUM) ) { goto fail; } sprintf(ttydev+NUM,"%d",ptsnum); MSG("trying %s... ",ttydev); tfd = open(ttydev,O_RDWR); if (tfd == -1) { goto fail; } MSG("done!\n"); if (pfnp) { strcpy(pfnp,ptydev); } if (tfnp) { strcpy(tfnp,ttydev); } if (pfdp) { *pfdp = pfd; } if (tfdp) { *tfdp = tfd; } return 0; fail: close(pfd); early_fail: MSG("failed!\n"); return -1; } #elif defined(PTY_UNIX98) #define MAX_PTSNUM 9999 static char ptydev[] = "/dev/ptmx"; /* 14 should be enough for /dev/pts/XXXX ; 32 is plenty */ #define TTYLEN 32 /* pfdp, tfdp, pfnp, tfnp are pointers to recipients for function results: pty fd, tty fd, pty name, tty name. The latter two must have space for TTYLEN characters */ static inline int get_pty_tty(int* pfdp, int* tfdp, char* pfnp, char* tfnp) { int pfd,tfd; int res; char *ttydev; MSG("gpt\n"); /* get the master */ pfd = open(ptydev, O_RDWR); if (pfd == -1) { goto early_fail; } /* grant the slave */ res = grantpt(pfd); if (res == -1) { goto fail; } /* unlock the pty */ res = unlockpt(pfd); if ( res == -1 ) { goto fail; } ttydev = ptsname(pfd); if ( (ttydev == NULL) || (strlen(ttydev) > TTYLEN) ) { goto fail; } MSG("trying %s... ",ttydev); tfd = open(ttydev,O_RDWR); if (tfd == -1) { goto fail; } MSG("done!\n"); if (pfnp) { strcpy(pfnp,ptydev); } if (tfnp) { strcpy(tfnp,ttydev); } if (pfdp) { *pfdp = pfd; } if (tfdp) { *tfdp = tfd; } return 0; fail: close(pfd); early_fail: MSG("failed!\n"); return -1; } #else # error No PTY handling specified. # error Define one of PTY_BSD_64, PTY_BSD_256, PTY_UNIX98_LINUX, PTY_UNIX98. #endif /* ------>8------>8------>8------>8------>8------>8------>8------>8------ */ static void drop_tty (void) { int fd; close(0); close(1); /*close(2);*/ fd = open("/dev/tty",O_RDWR); if (fd != -1) { ioctl(fd, TIOCNOTTY); close(fd); } LEAVE_PROCESS_GROUP(); } /* ------>8------>8------>8------>8------>8------>8------>8------>8------ */ #ifdef NEED_CFMAKERAW static inline void cfmakeraw (struct termios *termios_p) { termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP |INLCR|IGNCR|ICRNL|IXON); termios_p->c_oflag &= ~OPOST; termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); termios_p->c_cflag &= ~(CSIZE|PARENB); termios_p->c_cflag |= CS8; } #endif static inline void stty_raw (int fd) { static struct termios tios; static int err; err = tcgetattr(fd, &tios); if (err) { perror("stty_raw(get)"); MSG("error getting fd %d\n",fd); } cfmakeraw(&tios); err = tcsetattr(fd, TCSADRAIN, &tios); if (err) { perror("stty_raw(set)"); MSG("error setting fd %d\n",fd); } } /* ------>8------>8------>8------>8------>8------>8------>8------>8------ */ static volatile pid_t cpid[4]; static volatile int ncpid = 0; static void add_cpid (pid_t x) { MSG("register pid: %d\n",x); /* XXX: here, block signals */ cpid[ncpid++] = x ; /* XXX: here, restore signals */ } static void killall (void) { int i; for (i=0;i8------>8------>8------>8------>8------>8------>8------>8------ */ /* signal handlers */ static void sigchld (int i) { int pid, status; (void)i; /* tell GCC we use the argument. */ while ( (pid = waitpid(-1,&status,WNOHANG)) > 0 ) { MSG("pid %d exited with status %d\n",pid,status); #if 1 killall(); exit(WEXITSTATUS(status)); #endif } } static void terminate (int i) { killall(); exit(-i); } /* ------>8------>8------>8------>8------>8------>8------>8------>8------ */ /* fork()-based version of the proxy */ #ifdef USE_FORK #ifndef CAT_PATH /* This is really how cat works. My, I do hate C! */ #define CATBUFSZ 4096 static volatile void cat (void) { char buf[CATBUFSZ]; static ssize_t n, p, q; while (1) { n = read(0,buf,CATBUFSZ); if (n != -1) { for (p=0;p %d)\n",i,o); p = fork(); switch (p) { case -1: perror("fork"); killall(); abort_with(-errno, "cannot fork proxy1 (%d -> %d)\n", i, o); case 0: MSG("proxy1 (%d -> %d) reshuffling fds\n",i,o); dup2(i,0); dup2(o,1); for (j=3;j %d): cat\n",i,o); cat(); break; default: /* parent */ add_cpid(p); } } static inline void proxy (int n, int *fd) { int i; /* just fork the proxy halves */ for (i=0;i+18------>8------>8------>8------>8------>8------>8------>8------ */ /* select()-based version of the proxy */ #if defined(USE_SELECT) /* Here, we use select(2)-on-read to do byte-per-byte pty-to-pty proxy. To achieve buffering, the proxy would have to use non-blocking I/O Another way to run the proxy and achieve buffering, would be to not use select(2), but rather to fork two processes, each being a stupid cat < ptyX > ptyY; this may or not require subtle signal handling */ static void proxy (int n, int *fd) { static fd_set readfds; #if 1 static char buf[1]; #else static char buf[1024]; /* Most programs don't work with straightforward buffering. The solution to achieve buffering would be to use non-blocking I/O, and use select() to detect when write is possible... Looks pretty complicated. Try USE_FORK instead! */ #endif int i; for (;;) { FD_ZERO(&readfds); for (i=0;i8------>8------>8------>8------>8------>8------>8------>8------ */ enum cotty_mode_t { normal, /* -n (default), two tty's with process attached */ keep_current, /* -c two tty's, second process not attached */ one_shot, /* -1 one tty with process, controlled by current io */ pty_only, /* -p one pty with process, tty name printed */ direct /* -d one pty with process, one tty with process */ }; /* Number of TTYs to open depending on mode */ static inline int num_tty (enum cotty_mode_t mode) { switch (mode) { case one_shot: case pty_only: case direct: return 1; case normal: case keep_current: } return 2; } /* Number of processes to fork depending on mode */ static inline int num_process (enum cotty_mode_t mode) { switch (mode) { case one_shot: case pty_only: return 1; case normal: case keep_current: case direct: } return 2; } /* ------>8------>8------>8------>8------>8------>8------>8------>8------ */ static char *argv0; static char *sep; static int arg[2]; static enum cotty_mode_t mode = normal; /* options */ static int pfd[2]; static int tfd[2]; static char pty_name[2][TTYLEN]; static char tty_name[2][TTYLEN]; static char env_tty[TTYLEN+10]; /* "COTTY_TTY=/dev/ttyp??" */ static char env_cotty[TTYLEN+12]; /* "COTTY_COTTY=/dev/ttyp??" */ static int nullfd[3]; static int proxyfds[4]; /* ------>8------>8------>8------>8------>8------>8------>8------>8------ */ static inline void shuffle_fds (int process, enum cotty_mode_t mode) { int j; MSG("[%d] reshuffling fds\n",process); switch (mode) { case keep_current: /* first process like normal; second ain't do nothing */ if (process) { /* return; */ /* do not close pty/tty pair??? */ break; } case pty_only: case one_shot: /* only called once, but otherwise like normal */ case direct: case normal: /* drop current stdio */ drop_tty(); /* attach the tty to stdio */ MSG("[%d] %d -> %d\n",process,tfd[process],0); dup2(tfd[process],0); MSG("[%d] %d -> %d\n",process,tfd[process],1); dup2(0,1); break; } /* otherwise close allocated pty/tty pair(s) */ for (j=0;j8------>8------>8------>8------>8------>8------>8------>8------ */ /* Display version */ static void version (FILE* o) { fprintf(o,"cotty " COTTY_VERSION "\n"); } /* Display usage */ static void usage (FILE* o) { version(o); fprintf(o, "Usage: %s [-ncd] -sep- command1 -sep- command2\n" "or %s -[1p] command\n" "or %s [-h?]\n" "There ain't TFM to R, so UTSL for details. Sorry.\n", argv0,argv0,argv0); } /* ------>8------>8------>8------>8------>8------>8------>8------>8------ */ /* Parse command line */ static inline void parse_command_line (int argc, char **argv) { char option, *optstr; MSG("mode = default (normal)\n"); argv0 = argv[0]; arg[0] = 1; while (arg[0]= argc - 1) ) { MSG("arg[0]=%d, arg[1]=%d, argc=%d\n", arg[0],arg[1],argc); errprintf("Two commands required.\n"); goto bad_usage; } argv[arg[1]++] = NULL; } MSG("found arg[0]=%d\n",arg[0]); MSG("found arg[1]=%d\n",arg[1]); } /* ------>8------>8------>8------>8------>8------>8------>8------>8------ */ /* allocate pty/tty pair(s) */ static inline void allocate_pty_tty_pairs () { static int res; static int i,j; /* be sure that 0,1,2 are in use */ /* (this assumes the OS allocates lowest fd first) */ for (i=0;i<3;i++) { nullfd[i] = open("/dev/null",O_RDONLY); } /* Get pty/tty pair(s) */ for (i=0;i8------>8------>8------>8------>8------>8------>8------>8------ */ /* fork the commands */ static inline void fork_commands (char **argv) { static int i; static pid_t p; for (i=0;i8------>8------>8------>8------>8------>8------>8------>8------ */ /* run the proxy */ static inline void run_proxy (void) { signal(SIGTERM, terminate); signal(SIGHUP, terminate); signal(SIGINT, terminate); signal(SIGCHLD, sigchld); sigchld(0); switch (mode) { case pty_only: exit(0); case direct: return; case one_shot: close(tfd[0]); proxyfds[0]=0; proxyfds[1]=pfd[0]; proxyfds[2]=pfd[0]; proxyfds[3]=1; break; case keep_current: drop_tty(); case normal: close(tfd[0]); close(tfd[1]); proxyfds[0]=pfd[1]; proxyfds[1]=pfd[0]; proxyfds[2]=pfd[0]; proxyfds[3]=pfd[1]; } proxy(4,proxyfds); } /* ------>8------>8------>8------>8------>8------>8------>8------>8------ */ /* entry point and main stuff */ extern int main (int argc, char **argv) { MSG("cotty " COTTY_VERSION " started.\n"); parse_command_line(argc,argv); allocate_pty_tty_pairs(); fork_commands(argv); run_proxy(); wait(NULL); terminate(0); return 0; } /* ------>8------>8------>8------>8------>8------>8------>8------>8------ */ /* EOF */