/* * This file is part of "The Java Telnet Application". * * (c) Matthias L. Jugel, Marcus Mei�ner 1996-2002. All Rights Reserved. * * Please visit http://javatelnet.org/ for updates and contact. * The file was changed by Radek Polak to work as midlet in MIDP 1.0 * * This file has been modified by Karl von Randow for MidpSSH. * * --LICENSE NOTICE-- * 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 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * --LICENSE NOTICE-- * */ package telnet; import java.io.IOException; /** * This is a telnet protocol handler. The handler needs implementations for * several methods to handle the telnet options and to be able to read and write * the buffer. * <P> * <B>Maintainer: </B> Marcus Mei�ner * * @version $Id: TelnetProtocolHandler.java,v 1.1 2006/01/14 01:56:00 karl Exp $ * @author Matthias L. Jugel, Marcus Mei�ner */ public abstract class TelnetProtocolHandler { /** temporary buffer for data-telnetstuff-data transformation */ private byte[] tempbuf = new byte[0]; /** the data sent on pressing <RETURN>\n */ private byte[] crlf = new byte[2]; /** the data sent on pressing <LineFeed>\r */ private byte[] cr = new byte[2]; /** * Create a new telnet protocol handler. */ public TelnetProtocolHandler() { reset(); crlf[0] = 13; crlf[1] = 10; cr[0] = 13; cr[1] = 0; } /** * Get the current terminal type for TTYPE telnet option. * * @return the string id of the terminal */ protected abstract String getTerminalType(); /** * Get the current window size of the terminal for the NAWS telnet option. * * @return the size of the terminal as Dimension */ protected abstract Dimension getWindowSize(); /** * Set the local echo option of telnet. * * @param echo * true for local echo, false for no local echo */ protected abstract void setLocalEcho( boolean echo ); /** * Generate an EOR (end of record) request. For use by prompt displaying. */ protected abstract void notifyEndOfRecord(); /** * Send data to the remote host. * * @param b * array of bytes to send */ protected abstract void write( byte[] b ) throws IOException; /** * Reset the protocol handler. This may be necessary after the connection * was closed or some other problem occured. */ public void reset() { neg_state = 0; receivedDX = new byte[256]; sentDX = new byte[256]; receivedWX = new byte[256]; sentWX = new byte[256]; } // =================================================================== // the actual negotiation handling for the telnet protocol follows: // =================================================================== /** state variable for telnet negotiation reader */ private byte neg_state = 0; /** constants for the negotiation state */ private final static byte STATE_DATA = 0; private final static byte STATE_IAC = 1; private final static byte STATE_IACSB = 2; private final static byte STATE_IACWILL = 3; private final static byte STATE_IACDO = 4; private final static byte STATE_IACWONT = 5; private final static byte STATE_IACDONT = 6; private final static byte STATE_IACSBIAC = 7; private final static byte STATE_IACSBDATA = 8; private final static byte STATE_IACSBDATAIAC = 9; /** What IAC SB <xx>we are handling right now */ private byte current_sb; /** IAC - init sequence for telnet negotiation. */ private final static byte IAC = (byte) 255; /** [IAC] End Of Record */ private final static byte EOR = (byte) 239; /** [IAC] WILL */ private final static byte WILL = (byte) 251; /** [IAC] WONT */ private final static byte WONT = (byte) 252; /** [IAC] DO */ private final static byte DO = (byte) 253; /** [IAC] DONT */ private final static byte DONT = (byte) 254; /** [IAC] Sub Begin */ private final static byte SB = (byte) 250; private final static byte NOP = (byte) 241; /** [IAC] Sub End */ private final static byte SE = (byte) 240; /** Telnet option: binary mode */ private final static byte TELOPT_BINARY = (byte) 0; /* binary mode */ /** Telnet option: echo text */ private final static byte TELOPT_ECHO = (byte) 1; /* echo on/off */ /** Telnet option: sga */ private final static byte TELOPT_SGA = (byte) 3; /* supress go ahead */ /** Telnet option: End Of Record */ private final static byte TELOPT_EOR = (byte) 25; /* end of record */ /** Telnet option: Negotiate About Window Size */ private final static byte TELOPT_NAWS = (byte) 31; /* NA-WindowSize */ /** Telnet option: Terminal Type */ private final static byte TELOPT_TTYPE = (byte) 24; /* terminal type */ // private final static byte[] IACWILL = { // IAC, WILL // }; // // private final static byte[] IACWONT = { // IAC, WONT // }; // // private final static byte[] IACDO = { // IAC, DO // }; // // private final static byte[] IACDONT = { // IAC, DONT // }; // // private final static byte[] IACSB = { // IAC, SB // }; private final static byte[] IACSE = { IAC, SE }; /** Telnet option qualifier 'IS' */ private final static byte TELQUAL_IS = (byte) 0; /** Telnet option qualifier 'SEND' */ private final static byte TELQUAL_SEND = (byte) 1; /** What IAC DO(NT) request do we have received already ? */ private byte[] receivedDX; /** What IAC WILL/WONT request do we have received already ? */ private byte[] receivedWX; /** What IAC DO/DONT request do we have sent already ? */ private byte[] sentDX; /** What IAC WILL/WONT request do we have sent already ? */ private byte[] sentWX; /** * Send a Telnet Escape character (IAC <code>) */ public void sendTelnetControl( byte code ) throws IOException { byte[] b = new byte[2]; b[0] = IAC; b[1] = code; write( b ); } /** * Sends a Telnet NOP. * @throws IOException */ public void sendTelnetNOP() throws IOException { sendTelnetControl( NOP ); } /** * Handle an incoming IAC SB <type> <bytes> IAC SE * * @param type * type of SB * @param sbata * byte array as <bytes> * @param sbcount * nr of bytes. may be 0 too. */ private void handle_sb( byte type, byte[] sbdata, int sbcount ) throws IOException { switch ( type ) { case TELOPT_TTYPE: if ( sbcount > 0 && sbdata[0] == TELQUAL_SEND ) { write( new byte[] { IAC, SB, TELOPT_TTYPE, TELQUAL_IS } ); /* * FIXME: need more logic here if we use more than one * terminal type */ String ttype = getTerminalType(); write( ttype.getBytes() ); write( IACSE ); } } } /** * Do not send any notifications at startup. We do not know, whether the * remote client understands telnet protocol handling, so we are silent. * (This used to send IAC WILL SGA, but this is false for a compliant * client.) */ public void startup() throws IOException { } /** * Transpose special telnet codes like 0xff or newlines to values that are * compliant to the protocol. This method will also send the buffer * immediately after transposing the data. * * @param buf * the data buffer to be sent */ public void transpose( byte[] buf, int boffset, int length ) throws IOException { int i; byte[] nbuf, xbuf; int nbufptr = 0; int boffsetend = boffset + length; nbuf = new byte[length * 2]; // FIXME: buffer overflows possible for ( i = boffset; i < boffsetend; i++ ) { switch ( buf[i] ) { // Escape IAC twice in stream ... to be telnet protocol // compliant // this is there in binary and non-binary mode. case IAC: nbuf[nbufptr++] = IAC; nbuf[nbufptr++] = IAC; break; // We need to heed RFC 854. LF (\n) is 10, CR (\r) is 13 // we assume that the Terminal sends \n for lf+cr and \r for // just cr // KARL 27/1/2005 this is not the case for MidpSSH - we send just cr for crlf // linefeed+carriage return is CR LF */ case 13: // \r added by karl 27/1/2005 case 10: // \n if ( receivedDX[TELOPT_BINARY + 128] != DO ) { while ( nbuf.length - nbufptr < crlf.length ) { xbuf = new byte[nbuf.length * 2]; System.arraycopy( nbuf, 0, xbuf, 0, nbufptr ); nbuf = xbuf; } for ( int j = 0; j < crlf.length; j++ ) nbuf[nbufptr++] = crlf[j]; break; } else { // copy verbatim in binary mode. nbuf[nbufptr++] = buf[i]; } break; // carriage return is CR NUL */ /* case 13: // \r if ( receivedDX[TELOPT_BINARY + 128] != DO ) { while ( nbuf.length - nbufptr < cr.length ) { xbuf = new byte[nbuf.length * 2]; System.arraycopy( nbuf, 0, xbuf, 0, nbufptr ); nbuf = xbuf; } for ( int j = 0; j < cr.length; j++ ) nbuf[nbufptr++] = cr[j]; } else { // copy verbatim in binary mode. nbuf[nbufptr++] = buf[i]; } break; */ // all other characters are just copied default: nbuf[nbufptr++] = buf[i]; break; } } xbuf = new byte[nbufptr]; System.arraycopy( nbuf, 0, xbuf, 0, nbufptr ); write( xbuf ); } public void setCRLF( String xcrlf ) { crlf = xcrlf.getBytes(); } public void setCR( String xcr ) { cr = xcr.getBytes(); } /** * Handle telnet protocol negotiation. The buffer will be parsed and * necessary actions are taken according to the telnet protocol. See <A * HREF="RFC-Telnet-URL">RFC-Telnet </A> * * @param buf * the byte buffer used for negotiation * @param count * the amount of bytes in the buffer * @return a new buffer after negotiation */ public int negotiate( byte nbuf[], int noffset, int length ) throws IOException { byte sbbuf[] = new byte[tempbuf.length]; int count = tempbuf.length; byte[] buf = tempbuf; byte sendbuf[] = new byte[3]; byte b, reply; int sbcount = 0; int boffset = 0; int orignoffset = noffset; boolean dobreak = false; int noffsetend = noffset + length; if ( count == 0 ) // buffer is empty. return -1; while ( !dobreak && ( boffset < count ) && ( noffset < noffsetend ) ) { b = buf[boffset++]; // of course, byte is a signed entity (-128 -> 127) // but apparently the SGI Netscape 3.0 doesn't seem // to care and provides happily values up to 255 if ( b >= 128 ) b = (byte) ( (int) b - 256 ); switch ( neg_state ) { case STATE_DATA: if ( b == IAC ) { neg_state = STATE_IAC; dobreak = true; // leave the loop so we can sync. } else nbuf[noffset++] = b; break; case STATE_IAC: switch ( b ) { case IAC: neg_state = STATE_DATA; nbuf[noffset++] = IAC; break; case WILL: neg_state = STATE_IACWILL; break; case WONT: neg_state = STATE_IACWONT; break; case DONT: neg_state = STATE_IACDONT; break; case DO: neg_state = STATE_IACDO; break; case EOR: notifyEndOfRecord(); dobreak = true; // leave the loop so we can sync. neg_state = STATE_DATA; break; case SB: neg_state = STATE_IACSB; sbcount = 0; break; default: neg_state = STATE_DATA; break; } break; case STATE_IACWILL: switch ( b ) { case TELOPT_ECHO: reply = DO; setLocalEcho( false ); break; case TELOPT_SGA: reply = DO; break; case TELOPT_EOR: reply = DO; break; case TELOPT_BINARY: reply = DO; break; default: reply = DONT; break; } if ( reply != sentDX[b + 128] || WILL != receivedWX[b + 128] ) { sendbuf[0] = IAC; sendbuf[1] = reply; sendbuf[2] = b; write( sendbuf ); sentDX[b + 128] = reply; receivedWX[b + 128] = WILL; } neg_state = STATE_DATA; break; case STATE_IACWONT: switch ( b ) { case TELOPT_ECHO: setLocalEcho( true ); reply = DONT; break; case TELOPT_SGA: reply = DONT; break; case TELOPT_EOR: reply = DONT; break; case TELOPT_BINARY: reply = DONT; break; default: reply = DONT; break; } if ( reply != sentDX[b + 128] || WONT != receivedWX[b + 128] ) { sendbuf[0] = IAC; sendbuf[1] = reply; sendbuf[2] = b; write( sendbuf ); sentDX[b + 128] = reply; receivedWX[b + 128] = WILL; } neg_state = STATE_DATA; break; case STATE_IACDO: switch ( b ) { case TELOPT_ECHO: reply = WILL; setLocalEcho( true ); break; case TELOPT_SGA: reply = WILL; break; case TELOPT_TTYPE: reply = WILL; break; case TELOPT_BINARY: reply = WILL; break; case TELOPT_NAWS: Dimension size = getWindowSize(); receivedDX[b] = DO; if ( size == null ) { // this shouldn't happen write( new byte[] { IAC, WONT, TELOPT_NAWS } ); reply = WONT; sentWX[b] = WONT; break; } reply = WILL; sentWX[b] = WILL; sendbuf[0] = IAC; sendbuf[1] = WILL; sendbuf[2] = TELOPT_NAWS; write( sendbuf ); write( new byte[] { IAC, SB, TELOPT_NAWS, (byte) ( size.width >> 8 ), (byte) ( size.width & 0xff ), (byte) ( size.height >> 8 ), (byte) ( size.height & 0xff ), IAC, SE } ); break; default: reply = WONT; break; } if ( reply != sentWX[128 + b] || DO != receivedDX[128 + b] ) { sendbuf[0] = IAC; sendbuf[1] = reply; sendbuf[2] = b; write( sendbuf ); sentWX[b + 128] = reply; receivedDX[b + 128] = DO; } neg_state = STATE_DATA; break; case STATE_IACDONT: switch ( b ) { case TELOPT_ECHO: reply = WONT; setLocalEcho( false ); break; case TELOPT_SGA: reply = WONT; break; case TELOPT_NAWS: reply = WONT; break; case TELOPT_BINARY: reply = WONT; break; default: reply = WONT; break; } if ( reply != sentWX[b + 128] || DONT != receivedDX[b + 128] ) { write( new byte[] { IAC, reply, b } ); sentWX[b + 128] = reply; receivedDX[b + 128] = DONT; } neg_state = STATE_DATA; break; case STATE_IACSBIAC: if ( b == IAC ) { sbcount = 0; current_sb = b; neg_state = STATE_IACSBDATA; } else { //System.err.println( "(bad) " + b + " " ); neg_state = STATE_DATA; } break; case STATE_IACSB: switch ( b ) { case IAC: neg_state = STATE_IACSBIAC; break; default: current_sb = b; sbcount = 0; neg_state = STATE_IACSBDATA; break; } break; case STATE_IACSBDATA: switch ( b ) { case IAC: neg_state = STATE_IACSBDATAIAC; break; default: sbbuf[sbcount++] = b; break; } break; case STATE_IACSBDATAIAC: switch ( b ) { case IAC: neg_state = STATE_IACSBDATA; sbbuf[sbcount++] = IAC; break; case SE: handle_sb( current_sb, sbbuf, sbcount ); current_sb = 0; neg_state = STATE_DATA; break; case SB: handle_sb( current_sb, sbbuf, sbcount ); neg_state = STATE_IACSB; break; default: neg_state = STATE_DATA; break; } break; default: neg_state = STATE_DATA; break; } } // shrink tempbuf to new processed size. byte[] xb = new byte[count - boffset]; System.arraycopy( tempbuf, boffset, xb, 0, count - boffset ); tempbuf = xb; return ( noffset - orignoffset ); } public void inputfeed( byte[] b, int offset, int len ) { /*byte[] xb = new byte[tempbuf.length + len]; System.arraycopy( tempbuf, 0, xb, 0, tempbuf.length ); System.arraycopy( b, 0, xb, tempbuf.length, len ); tempbuf = xb;*/ byte[] xb = new byte[tempbuf.length + len]; System.arraycopy( tempbuf, 0, xb, 0, tempbuf.length ); System.arraycopy( b, offset, xb, tempbuf.length, len ); tempbuf = xb; } }