/*
* 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.awt.Dimension;
import java.lang.Byte;
/**
* 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 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;
/**
* 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 */
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);
}
/**
* 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);
}
}
}
/**
* 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[])
throws IOException
{
int count = tempbuf.length;
byte[] buf = tempbuf;
byte sendbuf[] = new byte[3];
byte b,reply;
int boffset = 0, noffset = 0;
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)((int)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");
Dimension 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.width >> 8));
write((byte) (size.width & 0xff));
write((byte) (size.height >> 8));
write((byte) (size.height & 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;
}
public void inputfeed(byte[] b, 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;
}
}