001: package kawa; // For now
002:
003: /** Encapsulates the state of a telnet connection.
004: * When run as an application, is a a minimal telnet client. */
005:
006: // Some ideas from: Flanagan: "Java Examples in a Nutshell" (O'Reilly, 1997),
007: // example 9-8.
008: public class Telnet implements Runnable {
009: boolean isServer;
010: final static int SE = 240; // End of subnegotiation parameters.
011: final static int NOP = 241; // No operation.
012: /*
013: Data Mark 242 The data stream portion of a Synch.
014: This should always be accompanied
015: by a TCP Urgent notification.
016: Break 243 NVT character BRK.*/
017: final static int IP = 244; // Interrupt Process.
018: final static int EOF = 236; // End of file.
019:
020: /** Indicates that what follows is subnegotiation of the indicated option. */
021: final static int SB = 250;
022:
023: /** Indicates the desire to begin performing, or confirmation that
024: you are now performing, the indicated option. */
025: public static final int WILL = 251;
026: /** Indicates the refusal to perform,or continue performing, the
027: indicated option. */
028: public static final int WONT = 252;
029:
030: /** Indicates the request that the other party perform, or
031: confirmation that you are expecting the other party to perform, the
032: indicated option. */
033: public static final int DO = 253;
034:
035: public static final int DONT = 254;
036: /** Indicates the demand that the other party stop performing,
037: or confirmation that you are no longer expecting the other party
038: to perform, the indicated option. */
039:
040: /** The "Interpret As Command" prefix code. */
041: final static int IAC = 255;
042:
043: // Various options.
044: public static final int ECHO = 1;
045: public static final int SUPPRESS_GO_AHEAD = 3;
046: final static int TM = 6; /* timing mark */
047: final static int TTYPE = 24; /* terminal type */
048: final static int NAWS = 31; /* window size */
049: final static int LINEMODE = 34;
050:
051: /* DEBUGGING:
052: public static String toString(int code)
053: {
054: switch (code)
055: {
056: case DO: return "DO";
057: case DONT: return "DONT";
058: case WILL: return "WILL";
059: case WONT: return "WONT";
060: case ECHO: return "ECHO";
061: case LINEMODE: return "LINEMODE";
062: case TTYPE: return "TTYPE";
063: case NAWS: return "NAWS";
064: case SUPPRESS_GO_AHEAD: return "SUPPRESS_GO_AHEAD";
065: default: return Integer.toString(code);
066: }
067: }
068: */
069:
070: public short windowHeight, windowWidth;
071: public byte[] terminalType;
072: final byte preferredLineMode = 3; // EDIT+TRAPSIG
073:
074: java.io.InputStream sin;
075: java.io.OutputStream sout;
076:
077: TelnetInputStream in;
078: TelnetOutputStream out;
079:
080: public TelnetInputStream getInputStream() {
081: return in;
082: }
083:
084: public TelnetOutputStream getOutputStream() {
085: return out;
086: }
087:
088: /** Used to store the the current state of negotiation of telnet options.
089: * For example, for option LINEMODE (34), (telnet_options_state[34] & 7)
090: * is the state of the option on this side, and
091: * ((telnet_options_state[34] >> 3) & 7) is the state on the other side.
092: * The 3 bits for each side can be any of OPTION_NO though OPTION_YES.
093: * The option is only enabled if the value is OPTION_YES.
094: * See RFC 1143. */
095: final byte[] optionsState = new byte[256];
096:
097: /** The option is disabled, and no negotiating is in progress. */
098: final static int OPTION_NO = 0;
099:
100: /** We sent out DONT/WONT and are waiting for confirming WONT/DONT. */
101: final static int OPTION_WANTNO = 1;
102:
103: /** Like WANTNO, but we changed our mind. */
104: final static int OPTION_WANTNO_OPPOSITE = 2;
105:
106: /** We sent out DO/WILL and are waiting for confirming WILL/DO. */
107: final static int OPTION_WANTYES = 3;
108:
109: /** Like WANTYES, but we changed our mind. */
110: final static int OPTION_WANTYES_OPPOSITE = 4;
111:
112: /** The option is enabled, and no negotiating is in progress. */
113: final static int OPTION_YES = 5;
114:
115: /** Actually (try to) change the state for an option.
116: * Return false is we don't know how or don't want to.
117: * command is DO if we're enabling on this side;
118: * DONT if we're disabling on this side;
119: * WILL if we're enabling for the other side;
120: * WONT if we're disabling for the other side.
121: *
122: * You should not call this function directly.
123: * Instead, call request to send a request to the other side
124: * (but with DO/WILL and DONT/WONT switched). Then, when
125: * confirmation comes back, it is handled by the handle method, which
126: * calls change.
127: * The optionsState array may not have been updated yet.
128: */
129: boolean change(int command, int option) {
130: if (option == TM)
131: return true;
132: if (isServer && option == NAWS)
133: return true;
134: if (isServer && command == WILL && option == LINEMODE) {
135: byte[] buf = new byte[2];
136: buf[0] = 1; // MODE
137: buf[1] = preferredLineMode;
138: try {
139: out.writeSubCommand(LINEMODE, buf);
140: } catch (java.io.IOException ex) {
141: // Ignore it - I guess we'll do without.
142: }
143: return true;
144: }
145: if (isServer && command == WILL && option == TTYPE) {
146: byte[] buf = new byte[1];
147: buf[0] = 1; // Request SEND terminal-type.
148: try {
149: out.writeSubCommand(option, buf);
150: } catch (java.io.IOException ex) {
151: // Ignore it - I guess we'll do without.
152: }
153: return true;
154: }
155: if (!isServer && option == ECHO) {
156: if (command == DO)
157: return false;
158: if (command == WILL)
159: return true;
160: }
161: return false;
162: }
163:
164: /** Handle a sub-command (SB-sequence) that we received. */
165:
166: public void subCommand(byte[] buf, int off, int len) {
167: int command = buf[off];
168: switch (command) {
169: case NAWS:
170: if (len == 5) {
171: windowWidth = (short) ((buf[1] << 8) + (buf[2] & 0xFF));
172: windowHeight = (short) ((buf[3] << 8) + (buf[4] & 0xFF));
173: /*
174: System.err.println("Window size: w:"
175: +windowWidth+"*h:"+windowHeight);
176: */
177: return;
178: }
179: break;
180: case TTYPE:
181: byte[] type = new byte[len - 1];
182: System.arraycopy(buf, 1, type, 0, len - 1);
183: terminalType = type;
184: System.err.println("terminal type: '" + new String(type)
185: + "'");
186: return;
187: case LINEMODE:
188: ///*
189: System.err.println("SBCommand LINEMODE " + buf[1] + " len:"
190: + len);
191: if (buf[1] == 3) // SLC
192: {
193: for (int i = 2; i + 2 < len; i += 3) {
194: System.err.println(" " + buf[i] + "," + buf[i + 1]
195: + "," + buf[i + 2]);
196: }
197: return;
198: }
199: //*/
200: break;
201: }
202: }
203:
204: /** Handle a request from the other side.
205: * Command is one of DO, DONT, WILL, WONT. */
206: void handle(int command, int option) throws java.io.IOException {
207: // True if the other side wants to change itself I.e. we got WILL/WONT);
208: // false if it wants us to change (i.e. we got DO/DONT).
209: boolean otherSide = command < DO;
210:
211: // True if DO or WILL; false if DONT or WONT.
212: boolean wantOn = (command & 1) != 0;
213: byte state = optionsState[option];
214: // System.err.println("telnet handle "+toString(command)+", "+toString(option));
215: if (otherSide)
216: state >>= 3;
217: switch ((state >> 3) & 7) {
218: case OPTION_YES:
219: if (wantOn)
220: return; // Nothing to do.
221: // Got a DONT or WONT. Disable without arguing.
222: state = OPTION_NO;
223: change(command, option);
224: out.writeCommand(otherSide ? DONT : WONT, option);
225: break;
226: case OPTION_NO:
227: if (!wantOn)
228: return; // Nothing to do.
229: if (change(command, option)) {
230: state = OPTION_YES;
231: out.writeCommand(otherSide ? DO : WILL, option); // Confirm.
232: } else {
233: out.writeCommand(otherSide ? Telnet.DONT : Telnet.WONT,
234: option);
235: }
236: break;
237: case OPTION_WANTNO:
238: state = OPTION_NO;
239: break;
240: case OPTION_WANTNO_OPPOSITE:
241: // if (goalState) Error: DONT/WONT answered by WILL/DO.
242: // Maybe some packets crossed in the mail.
243: // Presumably the other side will take our original
244: // request as de-conformation. In any case:
245: state = OPTION_WANTYES;
246: out.writeCommand(otherSide ? Telnet.DO : Telnet.WILL,
247: option);
248: break;
249: case OPTION_WANTYES:
250: if (wantOn) {
251: state = OPTION_YES;
252: change(command, option);
253: } else
254: state = OPTION_NO; // Declined.
255: break;
256: case OPTION_WANTYES_OPPOSITE:
257: if (wantOn) {
258: state = OPTION_WANTNO;
259: out.writeCommand(otherSide ? DONT : WONT, option);
260: } else {
261: state = OPTION_NO;
262: }
263: break;
264: }
265: if (otherSide)
266: state = (byte) ((optionsState[option] & 0xC7) | (state << 3));
267: else
268: state = (byte) ((optionsState[option] & 0xF8) | state);
269: optionsState[option] = state;
270: }
271:
272: /** Request (from this side) a new option state.
273: * Command is one of DO, DONT, WILL, WONT. */
274: public void request(int command, int option)
275: throws java.io.IOException {
276: // System.err.println("telnet request "+toString(command)+", "+toString(option));
277: // True if we want other side to change,
278: // false if we want this side to change.
279: boolean otherSide = command >= DO;
280:
281: // True for DO, WILL; false for DONT or WONT.
282: boolean wantOn = (command & 1) != 0;
283:
284: byte state = optionsState[option];
285: if (otherSide)
286: state >>= 3;
287:
288: switch (state & 7) {
289: case OPTION_NO:
290: if (wantOn) {
291: state = OPTION_WANTYES;
292: out.writeCommand(command, option);
293: }
294: // else: Redundant - already disabled.
295: break;
296: case OPTION_YES:
297: if (!wantOn) {
298: state = OPTION_WANTNO;
299: out.writeCommand(command, option);
300: }
301: // else: Redundant - already enabled.
302: break;
303: case OPTION_WANTNO:
304: if (wantOn)
305: state = OPTION_WANTNO_OPPOSITE;
306: // else: Error/redundant - already want to disable.
307: break;
308: case OPTION_WANTNO_OPPOSITE:
309: if (!wantOn)
310: state = OPTION_WANTNO;
311: // else: Error/redundant - already want to enable
312: break;
313: case OPTION_WANTYES:
314: if (!wantOn)
315: state = OPTION_WANTYES_OPPOSITE;
316: // else: Error/redundant - already want to disable.
317: case OPTION_WANTYES_OPPOSITE:
318: if (wantOn)
319: state = OPTION_WANTYES;
320: // else: Error/redundant - already want to enable
321: break;
322: }
323:
324: if (otherSide)
325: state = (byte) ((optionsState[option] & 0xC7) | (state << 3));
326: else
327: state = (byte) ((optionsState[option] & 0xF8) | state);
328: optionsState[option] = state;
329: }
330:
331: static void usage() {
332: System.err.println("Usage: [java] kawa.Telnet HOST [PORT#]");
333: System.exit(-1);
334: }
335:
336: public static void main(String[] args) {
337: if (args.length == 0)
338: usage();
339: String host = args[0];
340: int port = 23;
341: if (args.length > 1) {
342: port = Integer.parseInt(args[1]);
343: }
344: try {
345: java.net.Socket socket = new java.net.Socket(host, port);
346: Telnet telnet = new Telnet(socket, false);
347: TelnetOutputStream tout = telnet.getOutputStream();
348: Thread t = new Thread(telnet);
349:
350: t.setPriority(Thread.currentThread().getPriority() + 1);
351: t.start();
352:
353: byte[] buffer = new byte[1024];
354: for (;;) {
355: int ch = System.in.read();
356: if (ch < 0)
357: break; // send EOF FIXME
358: buffer[0] = (byte) ch;
359: int avail = System.in.available();
360: if (avail > 0) {
361: if (avail > buffer.length - 1)
362: avail = buffer.length - 1;
363: avail = System.in.read(buffer, 1, avail);
364: }
365: tout.write(buffer, 0, avail + 1);
366: }
367: t.stop();
368: } catch (Exception ex) {
369: System.err.println(ex);
370: }
371: }
372:
373: public Telnet(java.net.Socket socket, boolean isServer)
374: throws java.io.IOException {
375: sin = socket.getInputStream();
376: sout = socket.getOutputStream();
377: out = new TelnetOutputStream(sout);
378: in = new TelnetInputStream(sin, this );
379: this .isServer = isServer;
380: }
381:
382: public void run() {
383: try {
384: TelnetInputStream tin = getInputStream();
385: byte[] buffer = new byte[1024];
386: for (;;) {
387: int ch = tin.read();
388: if (ch < 0)
389: break; // ??? FIXME
390: buffer[0] = (byte) ch;
391: int avail = tin.available();
392: if (avail > 0) {
393: if (avail > buffer.length - 1)
394: avail = buffer.length - 1;
395: avail = tin.read(buffer, 1, avail);
396: }
397: System.out.write(buffer, 0, avail + 1);
398: }
399: } catch (java.io.IOException ex) {
400: System.err.println(ex);
401: System.exit(-1);
402: }
403: }
404: }
|