aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/de/mud/telnet/TelnetProtocolHandler.java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/de/mud/telnet/TelnetProtocolHandler.java')
-rw-r--r--app/src/main/java/de/mud/telnet/TelnetProtocolHandler.java678
1 files changed, 678 insertions, 0 deletions
diff --git a/app/src/main/java/de/mud/telnet/TelnetProtocolHandler.java b/app/src/main/java/de/mud/telnet/TelnetProtocolHandler.java
new file mode 100644
index 0000000..74f08bb
--- /dev/null
+++ b/app/src/main/java/de/mud/telnet/TelnetProtocolHandler.java
@@ -0,0 +1,678 @@
+/*
+ * 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 &lt;type&gt; &lt;bytes&gt; IAC SE
+ * @param type type of SB
+ * @param sbata byte array as &lt;bytes&gt;
+ */
+ 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;
+ }
+}