/* Code graciously borrowed and converted from MUD++ */ /* * Pseudo-terminals - Whheee! * This code is not very obvious. * It is concerned with handled pseudo-terminals (pty for short) * To run terminal emulation across a standard network descriptor * we open a master pty, fork a slave which opens the slave side of the pty, * maps the pty onto stdin/sdtdout/stderr and then execs the shell process. * Code for BSD is completely different from SYSV so please report problems. * Recommended reading: * -Stevens' UNIX Network Programming * -Berkeley Software Distribution source code (telnetd and rlogind) */ #include <fcntl.h> #include <termio.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <signal.h> #include <errno.h> #include <sys/time.h> #include <sys/file.h> #include <arpa/telnet.h> int route_io( int, int ); /* * Mini-telnetd * This is not meant to be an attempt to implement telnetd * Berkeley telnetd is about 7000 lines of code. A lot of it is * conditional switches for various non-standard OS. I just want * to implement enough to be functional. With time it will probably * grow as reports come in from OS that need different stuff. */ #define ST_DATA 0 #define ST_IAC 1 #define ST_SE 2 #define ST_OPT 3 #define ST_HOW 4 #define ST_WILL 5 #define ST_WONT 6 #define ST_DO 7 #define ST_DONT 8 #define ST_CRLF 9 #define ST_IP 10 int route_io( int fd1, int fd2 ) { struct timeval sleep_time; fd_set r_set, w_set, o_set; int state = ST_DATA; char netbuf[1024]; char netbuf2[1024]; char ptybuf[1024]; char ptybuf2[1024 * 2]; int fd1eofd = 0; int fd2eofd = 0; int netbytes = 0; int ptybytes = 0; int maxdesc = fd1; if ( maxdesc < fd2 ) maxdesc = fd2; FD_ZERO( &r_set ); FD_ZERO( &w_set ); FD_ZERO( &o_set ); for ( ;; ) { sleep_time.tv_sec = 1; sleep_time.tv_usec = 0; FD_SET( fd1, &r_set ); FD_SET( fd1, &w_set ); FD_SET( fd1, &o_set ); FD_SET( fd2, &r_set ); FD_SET( fd2, &w_set ); /* * First sleep until data to be read. Then poll the writeable * descriptors too. This is best for conserving CPU and gives * the best interactive performance from my tests. Of course * data only gets written on 'read' pulses but testing hasn't * shown this to be a problem. * Don't bother checking error codes here. */ select( maxdesc + 1, &r_set, 0, 0, &sleep_time ); /* Now do the write poll which almost always returns ready. */ sleep_time.tv_sec = 1; sleep_time.tv_usec = 0; while ( select( maxdesc + 1, 0, &w_set, &o_set, &sleep_time ) < 0 ) { if ( errno == EINTR ) continue; perror( "route_io:select" ); return -1; } if ( FD_ISSET( fd1, &o_set ) ) { perror( "Got OOB data.\n" ); // add code to deal with it soon } if ( FD_ISSET( fd1, &r_set ) && netbytes == 0 ) { int bytes = 0; int error; while ( 1 ) { if ( ( error = read( fd1, netbuf + bytes, sizeof( netbuf ) ) ) > 0 ) bytes += error; else if ( !error ) { fd1eofd = 1; netbytes = bytes; break; } else { if ( errno == EAGAIN || errno == EWOULDBLOCK ) { netbytes = bytes; break; } else if ( errno == ECONNRESET ) { fd1eofd = 1; netbytes = 0; break; } else if ( errno == EINTR ) continue; else { netbytes = error; break; } } } if ( netbytes < 0 ) { perror( "route_io:read-from-fd1" ); return -1; } else if ( netbytes == 0 ) if ( fd1eofd ) break; } if ( FD_ISSET( fd2, &w_set ) && netbytes > 0 ) { int i = 0; int j = 0; int ch; /* * After about 80% I looked at Berkeley telnetd and was surprised * that it uses a similar state machine. Also the CRLF * stuff here is based on my understanding of Berkeley sources. */ while ( i < netbytes ) { ch = netbuf[i++] & 0377; switch ( state ) { default: case ST_CRLF: /* Skip '\n' if '\r\n' */ state = ST_DATA; if ( ch == '\n' || ch == '\0' ) continue; /* Fall through to data */ case ST_DATA: if ( ch == IAC ) { state = ST_IAC; continue; } else if ( ch == '\r' ) state = ST_CRLF; netbuf2[j++] = ch; continue; case ST_IAC: /* Messy. Eat up IACs for now. */ if ( ch == SB ) state = ST_SE; else if ( ch == IP ) { /* Kludgy! Will handle data-mark and urgent * data later, right now just kill the shell. */ exit( 0 ); } else if ( ch == IAC ) /* double IAC can happen */ { netbuf2[j++] = ch; state = ST_DATA; } else state = ST_OPT; break; case ST_OPT: case ST_DO: case ST_DONT: case ST_WILL: case ST_WONT: /* will/wont/do/dont - discarding for now */ state = ST_DATA; break; case ST_SE: if ( ch == SE ) state = ST_DATA; break; } } /* If error assume shell exited */ { int error; int bytes = 0; int max_write = 1024; if ( j < max_write ) max_write = j; while ( 1 ) { error = write( fd2, netbuf2 + bytes, max_write ); if ( error >= 0 ) { bytes += error; if ( bytes >= j ) { break; } else max_write = j - bytes; continue; } else { if ( errno == EAGAIN || errno == EINTR ) continue; else { perror( "fd2 write" ); return -1; } } } netbytes = 0; } } // Pty has data if ( FD_ISSET( fd2, &r_set ) && ptybytes == 0 ) { int bytes = 0; int error; while ( 1 ) { error = read( fd2, ptybuf + bytes, sizeof( ptybuf ) - bytes ); if ( error > 0 ) { bytes += error; } else if ( !error ) { fd2eofd = 1; ptybytes = bytes; break; } else { if ( errno == EAGAIN || errno == EWOULDBLOCK ) { ptybytes = bytes; break; } else if ( errno == ECONNRESET ) { fd2eofd = 1; ptybytes = 0; break; } else if ( errno == EINTR ) continue; else { ptybytes = error; break; } } } } if ( ptybytes < 0 ) { perror( "fd2 read" ); return -1; } else if ( ptybytes == 0 ) { if ( fd2eofd ) break; } if ( FD_ISSET( fd1, &w_set ) && ptybytes > 0 ) { int i = 0; int j = 0; int ch; /* * This little snippet is from reading Berkeley sources * trying to understand the telnet protocol. * If we were supporting binary mode (RFC 856) we would need * more than what I have. IAC is a legal byte in binary * mode so if we get double IAC we just pass one thru. * --- * Concerning CRLF, since we are the server (UNIX side) * we only expect '\n' so translate for client. If we * see '\r' from server side, ignore since it was probably * part of some other app trying to do translation like us. * This is only my understanding, which is very limited. */ while ( i < ptybytes ) { ch = ptybuf[i++] & 0377; if ( ch == IAC ) ptybuf2[j++] = ch; else if ( ch == '\n' ) { ptybuf2[j++] = '\r'; ptybuf2[j++] = ch; } else if ( ch != '\r' ) ptybuf2[j++] = ch; } { int error; int bytes = 0; int max_write = 1024; if ( j < max_write ) max_write = j; while ( 1 ) { error = write( fd1, ( ptybuf2 + bytes ), max_write ); if ( error >= 0 ) { bytes += error; if ( bytes >= j ) { break; } else { max_write = j - bytes; } continue; } else { if ( errno == EAGAIN || errno == EINTR ) continue; else { perror( "write-to-fd1" ); return -1; } } } } ptybytes = 0; } } return 0; }