content: /*
   * 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 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 &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) {
      	break;
      	// FIXME: albb0920: 
      	//For some reason, maple3-itoc BBSs misunderstood our reply
      	// I think is because we replied too slow, since their implement simpilly ignores reply
      	// No BBS really care our terminal type anyway.. , before we can fix this, do nothing
      	
          //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;
    }
  }
  