view 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 source

/*
 * 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;
    }
}