123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- package kawa; // For now
- /** Encapsulates the state of a telnet connection.
- * When run as an application, is a a minimal telnet client. */
- // Some ideas from: Flanagan: "Java Examples in a Nutshell" (O'Reilly, 1997),
- // example 9-8.
- public class Telnet implements Runnable
- {
- boolean isServer;
- final static int SE = 240; // End of subnegotiation parameters.
- final static int NOP = 241; // No operation.
- /*
- Data Mark 242 The data stream portion of a Synch.
- This should always be accompanied
- by a TCP Urgent notification.
- Break 243 NVT character BRK.*/
- final static int IP = 244; // Interrupt Process.
- final static int EOF = 236; // End of file.
- /** Indicates that what follows is subnegotiation of the indicated option. */
- final static int SB = 250;
- /** Indicates the desire to begin performing, or confirmation that
- you are now performing, the indicated option. */
- public static final int WILL = 251;
- /** Indicates the refusal to perform,or continue performing, the
- indicated option. */
- public static final int WONT = 252;
- /** Indicates the request that the other party perform, or
- confirmation that you are expecting the other party to perform, the
- indicated option. */
- public static final int DO = 253;
- public static final int DONT = 254;
- /** Indicates the demand that the other party stop performing,
- or confirmation that you are no longer expecting the other party
- to perform, the indicated option. */
- /** The "Interpret As Command" prefix code. */
- final static int IAC = 255;
- // Various options.
- public static final int ECHO = 1;
- public static final int SUPPRESS_GO_AHEAD = 3;
- final static int TM = 6; /* timing mark */
- final static int TTYPE = 24; /* terminal type */
- final static int NAWS = 31; /* window size */
- final static int LINEMODE = 34;
- /* DEBUGGING:
- public static String toString(int code)
- {
- switch (code)
- {
- case DO: return "DO";
- case DONT: return "DONT";
- case WILL: return "WILL";
- case WONT: return "WONT";
- case ECHO: return "ECHO";
- case LINEMODE: return "LINEMODE";
- case TTYPE: return "TTYPE";
- case NAWS: return "NAWS";
- case SUPPRESS_GO_AHEAD: return "SUPPRESS_GO_AHEAD";
- default: return Integer.toString(code);
- }
- }
- */
- public short windowHeight, windowWidth;
- public byte[] terminalType;
- final byte preferredLineMode = 3; // EDIT+TRAPSIG
- java.io.InputStream sin;
- java.io.OutputStream sout;
- TelnetInputStream in;
- TelnetOutputStream out;
- public TelnetInputStream getInputStream()
- {
- return in;
- }
- public TelnetOutputStream getOutputStream()
- {
- return out;
- }
- /** Used to store the the current state of negotiation of telnet options.
- * For example, for option LINEMODE (34), (telnet_options_state[34] & 7)
- * is the state of the option on this side, and
- * ((telnet_options_state[34] >> 3) & 7) is the state on the other side.
- * The 3 bits for each side can be any of OPTION_NO though OPTION_YES.
- * The option is only enabled if the value is OPTION_YES.
- * See RFC 1143. */
- final byte[] optionsState = new byte[256];
- /** The option is disabled, and no negotiating is in progress. */
- final static int OPTION_NO = 0;
- /** We sent out DONT/WONT and are waiting for confirming WONT/DONT. */
- final static int OPTION_WANTNO = 1;
- /** Like WANTNO, but we changed our mind. */
- final static int OPTION_WANTNO_OPPOSITE = 2;
- /** We sent out DO/WILL and are waiting for confirming WILL/DO. */
- final static int OPTION_WANTYES = 3;
- /** Like WANTYES, but we changed our mind. */
- final static int OPTION_WANTYES_OPPOSITE = 4;
- /** The option is enabled, and no negotiating is in progress. */
- final static int OPTION_YES = 5;
- /** Actually (try to) change the state for an option.
- * Return false is we don't know how or don't want to.
- * command is DO if we're enabling on this side;
- * DONT if we're disabling on this side;
- * WILL if we're enabling for the other side;
- * WONT if we're disabling for the other side.
- *
- * You should not call this function directly.
- * Instead, call request to send a request to the other side
- * (but with DO/WILL and DONT/WONT switched). Then, when
- * confirmation comes back, it is handled by the handle method, which
- * calls change.
- * The optionsState array may not have been updated yet.
- */
- boolean change (int command, int option)
- {
- if (option == TM)
- return true;
- if (isServer && option == NAWS)
- return true;
- if (isServer && command == WILL && option == LINEMODE)
- {
- byte[] buf = new byte[2];
- buf[0] = 1; // MODE
- buf[1] = preferredLineMode;
- try
- {
- out.writeSubCommand(LINEMODE, buf);
- }
- catch (java.io.IOException ex)
- {
- // Ignore it - I guess we'll do without.
- }
- return true;
- }
- if (isServer && command == WILL && option == TTYPE)
- {
- byte[] buf = new byte[1];
- buf[0] = 1; // Request SEND terminal-type.
- try
- {
- out.writeSubCommand(option, buf);
- }
- catch (java.io.IOException ex)
- {
- // Ignore it - I guess we'll do without.
- }
- return true;
- }
- if (! isServer && option == ECHO)
- {
- if (command == DO)
- return false;
- if (command == WILL)
- return true;
- }
- return false;
- }
- /** Handle a sub-command (SB-sequence) that we received. */
- public void subCommand (byte[] buf, int off, int len)
- {
- int command = buf[off];
- switch (command)
- {
- case NAWS:
- if (len == 5)
- {
- windowWidth = (short) ((buf[1] << 8) + (buf[2] & 0xFF));
- windowHeight = (short) ((buf[3] << 8) + (buf[4] & 0xFF));
- /*
- System.err.println("Window size: w:"
- +windowWidth+"*h:"+windowHeight);
- */
- return;
- }
- break;
- case TTYPE:
- byte[] type = new byte[len-1];
- System.arraycopy(buf, 1, type, 0, len-1);
- terminalType = type;
- System.err.println("terminal type: '"+new String(type)+"'");
- return;
- case LINEMODE:
- ///*
- System.err.println("SBCommand LINEMODE "+buf[1]+" len:"+len);
- if (buf[1] == 3) // SLC
- {
- for (int i = 2; i+2 < len; i += 3)
- {
- System.err.println(" "+buf[i]+","+buf[i+1]+","+buf[i+2]);
- }
- return;
- }
- //*/
- break;
- }
- }
- /** Handle a request from the other side.
- * Command is one of DO, DONT, WILL, WONT. */
- void handle (int command, int option) throws java.io.IOException
- {
- // True if the other side wants to change itself I.e. we got WILL/WONT);
- // false if it wants us to change (i.e. we got DO/DONT).
- boolean otherSide = command < DO;
- // True if DO or WILL; false if DONT or WONT.
- boolean wantOn = (command & 1) != 0;
- byte state = optionsState[option];
- // System.err.println("telnet handle "+toString(command)+", "+toString(option));
- if (otherSide)
- state >>= 3;
- switch ((state >> 3) & 7)
- {
- case OPTION_YES:
- if (wantOn)
- return; // Nothing to do.
- // Got a DONT or WONT. Disable without arguing.
- state = OPTION_NO;
- change(command, option);
- out.writeCommand(otherSide ? DONT : WONT, option);
- break;
- case OPTION_NO:
- if (! wantOn)
- return; // Nothing to do.
- if (change (command, option))
- {
- state = OPTION_YES;
- out.writeCommand(otherSide ? DO : WILL, option); // Confirm.
- }
- else
- {
- out.writeCommand(otherSide ? Telnet.DONT : Telnet.WONT,
- option);
- }
- break;
- case OPTION_WANTNO:
- state = OPTION_NO;
- break;
- case OPTION_WANTNO_OPPOSITE:
- // if (goalState) Error: DONT/WONT answered by WILL/DO.
- // Maybe some packets crossed in the mail.
- // Presumably the other side will take our original
- // request as de-conformation. In any case:
- state = OPTION_WANTYES;
- out.writeCommand(otherSide ? Telnet.DO : Telnet.WILL,
- option);
- break;
- case OPTION_WANTYES:
- if (wantOn)
- {
- state = OPTION_YES;
- change (command, option);
- }
- else
- state = OPTION_NO; // Declined.
- break;
- case OPTION_WANTYES_OPPOSITE:
- if (wantOn)
- {
- state = OPTION_WANTNO;
- out.writeCommand(otherSide ? DONT : WONT, option);
- }
- else
- {
- state = OPTION_NO;
- }
- break;
- }
- if (otherSide)
- state = (byte) ((optionsState[option] & 0xC7) | (state << 3));
- else
- state = (byte) ((optionsState[option] & 0xF8) | state);
- optionsState[option] = state;
- }
- /** Request (from this side) a new option state.
- * Command is one of DO, DONT, WILL, WONT. */
- public void request (int command, int option) throws java.io.IOException
- {
- // System.err.println("telnet request "+toString(command)+", "+toString(option));
- // True if we want other side to change,
- // false if we want this side to change.
- boolean otherSide = command >= DO;
- // True for DO, WILL; false for DONT or WONT.
- boolean wantOn = (command & 1) != 0;
- byte state = optionsState[option];
- if (otherSide)
- state >>= 3;
- switch (state & 7)
- {
- case OPTION_NO:
- if (wantOn)
- {
- state = OPTION_WANTYES;
- out.writeCommand(command, option);
- }
- // else: Redundant - already disabled.
- break;
- case OPTION_YES:
- if (! wantOn)
- {
- state = OPTION_WANTNO;
- out.writeCommand(command, option);
- }
- // else: Redundant - already enabled.
- break;
- case OPTION_WANTNO:
- if (wantOn)
- state = OPTION_WANTNO_OPPOSITE;
- // else: Error/redundant - already want to disable.
- break;
- case OPTION_WANTNO_OPPOSITE:
- if (! wantOn)
- state = OPTION_WANTNO;
- // else: Error/redundant - already want to enable
- break;
- case OPTION_WANTYES:
- if (! wantOn)
- state = OPTION_WANTYES_OPPOSITE;
- // else: Error/redundant - already want to disable.
- case OPTION_WANTYES_OPPOSITE:
- if (wantOn)
- state = OPTION_WANTYES;
- // else: Error/redundant - already want to enable
- break;
- }
- if (otherSide)
- state = (byte) ((optionsState[option] & 0xC7) | (state << 3));
- else
- state = (byte) ((optionsState[option] & 0xF8) | state);
- optionsState[option] = state;
- }
- static void usage ()
- {
- System.err.println("Usage: [java] kawa.Telnet HOST [PORT#]");
- System.exit(-1);
- }
- public static void main (String[] args)
- {
- if (args.length == 0)
- usage();
- String host = args[0];
- int port = 23;
- if (args.length > 1)
- {
- port = Integer.parseInt(args[1]);
- }
- try
- {
- java.net.Socket socket = new java.net.Socket(host, port);
- Telnet telnet = new Telnet(socket, false);
- TelnetOutputStream tout = telnet.getOutputStream();
- Thread t = new Thread(telnet);
- t.setPriority(Thread.currentThread().getPriority() + 1);
- t.start();
- byte[] buffer = new byte[1024];
- for (;;)
- {
- int ch = System.in.read();
- if (ch < 0)
- break; // send EOF FIXME
- buffer[0] = (byte) ch;
- int avail = System.in.available();
- if (avail > 0)
- {
- if (avail > buffer.length-1)
- avail = buffer.length - 1;
- avail = System.in.read(buffer, 1, avail);
- }
- tout.write(buffer, 0, avail+1);
- }
- t.stop();
- }
- catch (Exception ex)
- {
- System.err.println(ex);
- }
- }
- public Telnet (java.net.Socket socket, boolean isServer)
- throws java.io.IOException
- {
- sin = socket.getInputStream();
- sout = socket.getOutputStream();
- out = new TelnetOutputStream(sout);
- in = new TelnetInputStream(sin, this);
- this.isServer = isServer;
- }
- public void run ()
- {
- try
- {
- TelnetInputStream tin = getInputStream();
- byte[] buffer = new byte[1024];
- for (;;)
- {
- int ch = tin.read();
- if (ch < 0)
- break; // ??? FIXME
- buffer[0] = (byte) ch;
- int avail = tin.available();
- if (avail > 0)
- {
- if (avail > buffer.length-1)
- avail = buffer.length - 1;
- avail = tin.read(buffer, 1, avail);
- }
- System.out.write(buffer, 0, avail+1);
- }
- }
- catch (java.io.IOException ex)
- {
- System.err.println(ex);
- System.exit(-1);
- }
- }
- }
|