/* * Copyright (c) 2003, James Bailie. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The name of James Bailie may not be used to endorse or promote * products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include struct termios old_termios; char device[ 11 ] = "/dev/ptyXX"; int user, root, fd[ 2 ]; /* * sigwinch declared volatile for safety because it is accessed * asynchronously by SIGWINCH_handler. */ volatile int sigwinch = 0; void term_reset() { /* * Reset the real tty's settings. */ if ( tcsetattr( STDIN_FILENO, TCSANOW, &old_termios ) < 0 ) { term_reset(); perror( "tcsetattr" ); exit( 1 ); } /* * Reset the slave tty's ownership and permissions. */ { struct group *group; int wheel_gid; group = getgrnam( "wheel" ); wheel_gid = ( group == NULL ? -1 : group->gr_gid ); seteuid( root ); chown( device, root, wheel_gid ); chmod( device, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH ); seteuid( user ); } } void open_pty() { { char *char1, *char2; /* * Set euid back to root, and manually search for a free pseudo-terminal. */ seteuid( root ); for( char1 = "pqrsPQRS"; *char1; ++char1 ) { device[ 8 ] = *char1; for( char2 = "0123456789abcdefghijklmnopqrstuv"; *char2; ++char2 ) { device[ 9 ] = *char2; if (( fd[ 0 ] = open( device, O_RDWR )) >= 0 ) goto SUCCESS; } } /* * If none of the system's ptys could be opened, print last error. */ if ( fd[ 0 ] < 0 ) { fprintf( stderr, "pty: out of ptys\n" ); exit( 1 ); } } SUCCESS: /* * Change device name to refer to slave tty, and show the user. */ device[ 5 ] = 't'; printf( "pty: slave device: %s\n", device ); printf( "pty: pid : %d\n", getpid() ); fflush( stdout ); /* * Secure the tty. */ { struct group *group= ( struct group *)getgrnam( "tty" ); int tty_gid = ( group == NULL ? -1 : group->gr_gid ); chown( device, user, tty_gid ); chmod( device, S_IRUSR | S_IWUSR | S_IWGRP ); revoke( device ); } if (( fd[ 1 ] = open( device, O_RDWR )) == -1 ) { perror( "open" ); exit( 1 ); } /* * Put the terminal device into raw mode. */ { struct termios termios; if ( tcgetattr( STDIN_FILENO, &termios ) < 0 ) { perror( "tcgetattr" ); exit( 1 ); } old_termios = termios; cfmakeraw( &termios ); termios.c_cc[ VMIN ] = 1; termios.c_cc[ VTIME ] = 0; if ( tcsetattr( STDIN_FILENO, TCSAFLUSH, &termios ) < 0 ) { term_reset(); perror( "tcsetattr" ); exit( 1 ); } } seteuid( user ); } void set_winsize() { struct winsize winsize; if ( ioctl( STDIN_FILENO, TIOCGWINSZ, ( char *)&winsize ) < 0 ) { term_reset(); perror( "ioctl" ); exit( 1 ); } if ( ioctl( fd[ 0 ], TIOCSWINSZ, ( char *)&winsize ) < 0 ) { term_reset(); perror( "ioctl" ); exit( 1 ); } /* * Since this variable is set inside the SIGWINCH signal handler, * there is a concurrency issue here, but the worst that could * happen is we miss processing a SIGWINCH arriving rapidly on the * heels of a previous SIGWINCH. */ sigwinch = 0; } void transfer_data() { int result, ilength, olength, count; char ibuffer[ 4096 ], *iptr; char obuffer[ 4096 ], *optr; fd_set rset, wset; ilength = olength = 0; iptr = ibuffer; optr = obuffer; for( ; ; ) { FD_ZERO( &rset ); FD_ZERO( &wset ); /* * By adding descriptors to the write set only when there is data * waiting to be written, we greatly reduce the number of cycles * pty consumes, as select would continually return because * the descriptors will always be writable unless we have managed * to fill the other process's input buffer. */ if ( ilength ) FD_SET( STDOUT_FILENO, &wset ); else FD_SET( fd[ 0 ], &rset ); if ( olength ) FD_SET( fd[ 0 ], &wset ); else FD_SET( STDIN_FILENO, &rset ); result = select( fd[ 0 ] + 1, &rset, &wset, NULL, NULL ); /* * Propagate sigwinches to slave device. */ if ( sigwinch ) set_winsize(); /* * If select() was interrupted by a signal, restart it, else if there * is an error condition, barf. */ if ( result == -1 ) { if ( errno == EINTR ) continue; term_reset(); perror( "select" ); exit( 1 ); } /* * If the input buffer is empty, copy data from pseudo-terminal to * stdout. */ if ( !ilength && FD_ISSET( fd[ 0 ], &rset )) { if (( count = read( fd[ 0 ], ibuffer, sizeof( ibuffer ))) < 0 ) { term_reset(); perror( "read" ); exit( 1 ); } if ( !count ) { close( fd[ 0 ] ); return; } ilength = count; } /* * Copy any waiting data from the input buffer to stdout. */ if ( ilength && FD_ISSET( STDOUT_FILENO, &wset )) { if (( count = write( STDOUT_FILENO, iptr, ilength )) < 0 ) { perror( "write" ); term_reset(); exit( 1 ); } if ( count ) { ilength -= count; iptr = ( ilength ? iptr + count : ibuffer ); } } /* * If output buffer is empty, copy data from stdin to output buffer. */ if ( !olength && FD_ISSET( STDIN_FILENO, &rset )) { if (( count = read( STDIN_FILENO, obuffer, sizeof( obuffer ))) < 0 ) { perror( "read" ); term_reset(); exit( 1 ); } if ( !count ) { close( fd[ 0 ] ); return; } olength = count; } /* * Copy any waiting data in output buffer to slave process. */ if ( olength && FD_ISSET( fd[ 0 ], &wset )) { if (( count = write( fd[ 0 ], optr, olength )) < 0 ) { term_reset(); perror( "write" ); exit( 1 ); } if ( count ) { olength -= count; optr = ( olength ? optr + count : obuffer ); } } } } void SIGWINCH_handler() { sigwinch = 1; } int main() { if ( !isatty( STDIN_FILENO ) ) { fprintf( stderr, "pty must be run from a terminal device.\n" ); exit( 1 ); } user = getuid(); root = geteuid(); /* * We would like SIGWINCH to interrupt select() in transfer_data(), so * that the signal can be immediately propagated to the slave process, */ { struct sigaction sigact; sigact.sa_handler = SIGWINCH_handler; sigemptyset( &sigact.sa_mask ); sigact.sa_flags = 0; sigaction( SIGWINCH, &sigact, NULL ); } open_pty(); set_winsize(); transfer_data(); term_reset(); return 0; }