/* * This file is part of "JTA - Telnet/SSH for the JAVA(tm) platform". * * (c) Matthias L. Jugel, Marcus Meißner 1996-2005. All Rights Reserved. * * Please visit http://javatelnet.org/ for updates and contact. * * --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 de.mud.telnet; import java.io.IOException; import java.util.ArrayList; /** * 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 Meissner * * @version $Id: TelnetProtocolHandler.java 503 2005-10-24 07:34:13Z marcus $ * @author Matthias L. Jugel, Marcus Meissner */ public abstract class TelnetProtocolHandler { /** contains the current revision id */ public final static String ID = "$Id: TelnetProtocolHandler.java 503 2005-10-24 07:34:13Z marcus $"; /** debug level */ private final static int debug = 0; /** 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 int[] 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; /** * Read the charset name from terminal. */ protected abstract String getCharsetName(); /** * Send one byte to the remote host. * @param b the byte to be sent * @see #write(byte[] b) */ private static byte[] one = new byte[1]; private void write(byte b) throws IOException { one[0] = b; write(one); } /** * 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; /** current SB negotiation buffer */ private byte[] sbbuf; /** 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; /** [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 */ /** Telnet option: CHARSET */ private final static byte TELOPT_CHARSET= (byte)42; /* charset */ 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 }; private final static byte CHARSET_ACCEPTED = (byte)2; private final static byte CHARSET_REJECTED = (byte)3; /** 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); } /** * Helper Method to convert ArrayList<Bytes> to Byte[] array. * @param byteArray * @return */ private byte[] arrayListToBytes(ArrayList<Byte> byteArray) { byte[] bytes = new byte[byteArray.size()]; for(int i = 0; i < byteArray.size(); i++) { bytes[i] = byteArray.get(i); } return bytes; } /** * Send the new Window Size (via NAWS) */ public void setWindowSize(int columns,int rows) throws IOException { if(debug > 2) System.err.println("sending NAWS"); if (receivedDX[TELOPT_NAWS] != DO) { System.err.println("not allowed to send NAWS? (DONT NAWS)"); return; } // Use List to hold and send entire sequence at one time. ArrayList<Byte> byteArray = new ArrayList<>(); byteArray.add(IAC); byteArray.add(SB); byteArray.add(TELOPT_NAWS); byteArray.add((byte) (columns >> 8)); byteArray.add((byte) (columns & 0xff)); byteArray.add((byte) (rows >> 8)); byteArray.add((byte) (rows & 0xff)); byteArray.add(IAC); byteArray.add(SE); // Write out as a single sequence. write(arrayListToBytes(byteArray)); } /** * Handle an incoming IAC SB <type> <bytes> IAC SE * @param type type of SB * @param sbdata byte array as <bytes> */ private void handle_sb(byte type, byte[] sbdata) throws IOException { if (debug > 1) System.err.println("TelnetIO.handle_sb("+type+")"); switch(type) { case TELOPT_TTYPE: { if(sbdata.length > 0 && sbdata[0]==TELQUAL_SEND) { // Use List to hold and send entire sequence at one time. ArrayList<Byte> byteArray = new ArrayList<>(); // Get Terminal Type /* FIXME: need more logic here if we use * more than one terminal type * should be option in connection setup. */ String ttype = getTerminalType(); if(ttype == null) { // Set Default of ansi for telnet. ttype = "ansi"; } byteArray.add(IAC); byteArray.add(SB); byteArray.add(TELOPT_TTYPE); byteArray.add(TELQUAL_IS); for(byte b : ttype.getBytes()) { byteArray.add(b); } byteArray.add(IAC); byteArray.add(SE); // Write out as a single sequence. write(arrayListToBytes(byteArray)); } } break; case TELOPT_CHARSET: { System.out.println("Got SB CHARSET"); // Use List to hold and send entire sequence at one time. ArrayList<Byte> byteArray = new ArrayList<>(); byteArray.add(IAC); byteArray.add(SB); byteArray.add(TELOPT_CHARSET); String charsetStr = new String(sbdata, "US-ASCII"); if(charsetStr.startsWith("TTABLE ")) { charsetStr = charsetStr.substring(7); } String[] charsets = charsetStr.split(charsetStr.substring(0, 0)); String myCharset = getCharsetName(); for(String charset : charsets) { if(charset.equals(myCharset)) { byteArray.add(CHARSET_ACCEPTED); for(byte b : charset.getBytes()) { byteArray.add(b); } byteArray.add(IAC); byteArray.add(SE); // Write out as a single sequence. write(arrayListToBytes(byteArray)); System.out.println("Sent our charset!"); return; } } // Rejected. byteArray.add(CHARSET_REJECTED); byteArray.add(IAC); byteArray.add(SE); // Write out as a single sequence. write(arrayListToBytes(byteArray)); } break; default: break; } } /** * 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) throws IOException { int i; byte[] nbuf,xbuf; int nbufptr=0; nbuf = new byte[buf.length*2]; // FIXME: buffer overflows possible for (i = 0; i < buf.length ; 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 // linefeed+carriage return is CR LF */ 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 nbuf the byte buffer put out after negotiation * @return number of bytes processed, 0 for none, and -1 for end of buffer. */ public int negotiate(byte nbuf[], int offset) throws IOException { int count = tempbuf.length; byte[] buf = tempbuf; byte sendbuf[] = new byte[3]; byte b,reply; int boffset = 0, noffset = offset; boolean dobreak = false; if (count == 0) // buffer is empty. return -1; while(!dobreak && (boffset < count) && (noffset < nbuf.length)) { 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)(b-256); if(debug > 2) { Byte B = Byte.valueOf(b); System.err.print("byte: " + B.intValue()+ " "); } 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: if(debug > 2) System.err.print("IAC "); neg_state = STATE_DATA; nbuf[noffset++]=IAC; break; case WILL: if(debug > 2) System.err.print("WILL "); neg_state = STATE_IACWILL; break; case WONT: if(debug > 2) System.err.print("WONT "); neg_state = STATE_IACWONT; break; case DONT: if(debug > 2) System.err.print("DONT "); neg_state = STATE_IACDONT; break; case DO: if(debug > 2) System.err.print("DO "); neg_state = STATE_IACDO; break; case EOR: if(debug > 1) System.err.print("EOR "); notifyEndOfRecord(); dobreak = true; // leave the loop so we can sync. neg_state = STATE_DATA; break; case SB: if(debug > 2) System.err.print("SB "); neg_state = STATE_IACSB; break; default: if(debug > 2) System.err.print("<UNKNOWN "+b+" > "); neg_state = STATE_DATA; break; } break; case STATE_IACWILL: switch(b) { case TELOPT_ECHO: if(debug > 2) System.err.println("ECHO"); reply = DO; setLocalEcho(false); break; case TELOPT_SGA: if(debug > 2) System.err.println("SGA"); reply = DO; break; case TELOPT_EOR: if(debug > 2) System.err.println("EOR"); reply = DO; break; case TELOPT_BINARY: if(debug > 2) System.err.println("BINARY"); reply = DO; break; default: if(debug > 2) System.err.println("<UNKNOWN,"+b+">"); reply = DONT; break; } if(debug > 1) System.err.println("<"+b+", WILL ="+WILL+">"); 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: if(debug > 2) System.err.println("ECHO"); setLocalEcho(true); reply = DONT; break; case TELOPT_SGA: if(debug > 2) System.err.println("SGA"); reply = DONT; break; case TELOPT_EOR: if(debug > 2) System.err.println("EOR"); reply = DONT; break; case TELOPT_BINARY: if(debug > 2) System.err.println("BINARY"); reply = DONT; break; default: if(debug > 2) System.err.println("<UNKNOWN,"+b+">"); 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: if(debug > 2) System.err.println("ECHO"); reply = WILL; setLocalEcho(true); break; case TELOPT_SGA: if(debug > 2) System.err.println("SGA"); reply = WILL; break; case TELOPT_TTYPE: if(debug > 2) System.err.println("TTYPE"); reply = WILL; break; case TELOPT_BINARY: if(debug > 2) System.err.println("BINARY"); reply = WILL; break; case TELOPT_NAWS: { if(debug > 2) System.err.println("NAWS"); // Use List to hold and send entire sequence at one time. ArrayList<Byte> byteArray = new ArrayList<>(); int[] size = getWindowSize(); receivedDX[b] = DO; if(size == null) { // this shouldn't happen byteArray.add(IAC); byteArray.add(WONT); byteArray.add(TELOPT_NAWS); // Write out as a single sequence. write(arrayListToBytes(byteArray)); reply = WONT; sentWX[b] = WONT; break; } reply = WILL; sentWX[b] = WILL; byteArray.add(IAC); byteArray.add(SB); byteArray.add(TELOPT_NAWS); byteArray.add((byte) (size[0] >> 8)); byteArray.add((byte) (size[0] & 0xff)); byteArray.add((byte) (size[1] >> 8)); byteArray.add((byte) (size[1] & 0xff)); byteArray.add(IAC); byteArray.add(SE); // Write out as a single sequence. write(arrayListToBytes(byteArray)); } break; default: if(debug > 2) System.err.println("<UNKNOWN,"+b+">"); 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: if(debug > 2) System.err.println("ECHO"); reply = WONT; setLocalEcho(false); break; case TELOPT_SGA: if(debug > 2) System.err.println("SGA"); reply = WONT; break; case TELOPT_NAWS: if(debug > 2) System.err.println("NAWS"); reply = WONT; break; case TELOPT_BINARY: if(debug > 2) System.err.println("BINARY"); reply = WONT; break; default: if(debug > 2) System.err.println("<UNKNOWN,"+b+">"); reply = WONT; break; } if(reply != sentWX[b+128] || DONT != receivedDX[b+128]) { write(IAC);write(reply);write(b); sentWX[b+128] = reply; receivedDX[b+128] = DONT; } neg_state = STATE_DATA; break; case STATE_IACSBIAC: if(debug > 2) System.err.println(""+b+" "); if (b == IAC) { sbbuf = new byte[0]; current_sb = b; neg_state = STATE_IACSBDATA; } else { System.err.println("(bad) "+b+" "); neg_state = STATE_DATA; } break; case STATE_IACSB: if(debug > 2) System.err.println(""+b+" "); switch (b) { case IAC: neg_state = STATE_IACSBIAC; break; default: current_sb = b; sbbuf = new byte[0]; neg_state = STATE_IACSBDATA; break; } break; case STATE_IACSBDATA: if (debug > 2) System.err.println(""+b+" "); switch (b) { case IAC: neg_state = STATE_IACSBDATAIAC; break; default: byte[] xsb = new byte[sbbuf.length+1]; System.arraycopy(sbbuf,0,xsb,0,sbbuf.length); sbbuf = xsb; sbbuf[sbbuf.length-1] = b; break; } break; case STATE_IACSBDATAIAC: if (debug > 2) System.err.println(""+b+" "); switch (b) { case IAC: neg_state = STATE_IACSBDATA; byte[] xsb = new byte[sbbuf.length+1]; System.arraycopy(sbbuf,0,xsb,0,sbbuf.length); sbbuf = xsb; sbbuf[sbbuf.length-1] = IAC; break; case SE: handle_sb(current_sb,sbbuf); current_sb = 0; neg_state = STATE_DATA; break; case SB: handle_sb(current_sb,sbbuf); neg_state = STATE_IACSB; break; default: neg_state = STATE_DATA; break; } break; default: if (debug > 1) System.err.println("This should not happen: "+neg_state+" "); 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 - offset; } 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,offset,xb,tempbuf.length,len); tempbuf = xb; } }