Mercurial > 510Connectbot
diff src/de/mud/telnet/TelnetProtocolHandler.java @ 0:0ce5cc452d02
initial version
author | Carl Byington <carl@five-ten-sg.com> |
---|---|
date | Thu, 22 May 2014 10:41:19 -0700 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/de/mud/telnet/TelnetProtocolHandler.java Thu May 22 10:41:19 2014 -0700 @@ -0,0 +1,789 @@ +/* + * 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; +/** + * 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); + } + + /** + * 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; + } + + write(IAC); write(SB); write(TELOPT_NAWS); + write((byte)(columns >> 8)); + write((byte)(columns & 0xff)); + write((byte)(rows >> 8)); + write((byte)(rows & 0xff)); + write(IAC); write(SE); + } + + + /** + * Handle an incoming IAC SB <type> <bytes> IAC SE + * @param type type of SB + * @param sbata 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) { + write(IACSB); write(TELOPT_TTYPE); write(TELQUAL_IS); + /* FIXME: need more logic here if we use + * more than one terminal type + */ + String ttype = getTerminalType(); + + if (ttype == null) ttype = "dumb"; + + write(ttype.getBytes()); + write(IACSE); + } + + break; + + case TELOPT_CHARSET: + System.out.println("Got SB 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)) { + write(IACSB); write(TELOPT_CHARSET); write(CHARSET_ACCEPTED); + write(charset.getBytes()); + write(IACSE); + System.out.println("Sent our charset!"); + return; + } + } + + write(IACSB); write(TELOPT_CHARSET); write(CHARSET_REJECTED); + write(IACSE); + 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 = new Byte(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"); + + int[] size = getWindowSize(); + receivedDX[b] = DO; + + if (size == null) { + // this shouldn't happen + write(IAC); + write(WONT); + write(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(IAC); write(SB); write(TELOPT_NAWS); + write((byte)(size[0] >> 8)); + write((byte)(size[0] & 0xff)); + write((byte)(size[1] >> 8)); + write((byte)(size[1] & 0xff)); + write(IAC); write(SE); + 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; + } +}