Telnet.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. package kawa; // For now
  2. /** Encapsulates the state of a telnet connection.
  3. * When run as an application, is a a minimal telnet client. */
  4. // Some ideas from: Flanagan: "Java Examples in a Nutshell" (O'Reilly, 1997),
  5. // example 9-8.
  6. public class Telnet implements Runnable
  7. {
  8. boolean isServer;
  9. final static int SE = 240; // End of subnegotiation parameters.
  10. final static int NOP = 241; // No operation.
  11. /*
  12. Data Mark 242 The data stream portion of a Synch.
  13. This should always be accompanied
  14. by a TCP Urgent notification.
  15. Break 243 NVT character BRK.*/
  16. final static int IP = 244; // Interrupt Process.
  17. final static int EOF = 236; // End of file.
  18. /** Indicates that what follows is subnegotiation of the indicated option. */
  19. final static int SB = 250;
  20. /** Indicates the desire to begin performing, or confirmation that
  21. you are now performing, the indicated option. */
  22. public static final int WILL = 251;
  23. /** Indicates the refusal to perform,or continue performing, the
  24. indicated option. */
  25. public static final int WONT = 252;
  26. /** Indicates the request that the other party perform, or
  27. confirmation that you are expecting the other party to perform, the
  28. indicated option. */
  29. public static final int DO = 253;
  30. public static final int DONT = 254;
  31. /** Indicates the demand that the other party stop performing,
  32. or confirmation that you are no longer expecting the other party
  33. to perform, the indicated option. */
  34. /** The "Interpret As Command" prefix code. */
  35. final static int IAC = 255;
  36. // Various options.
  37. public static final int ECHO = 1;
  38. public static final int SUPPRESS_GO_AHEAD = 3;
  39. final static int TM = 6; /* timing mark */
  40. final static int TTYPE = 24; /* terminal type */
  41. final static int NAWS = 31; /* window size */
  42. final static int LINEMODE = 34;
  43. /* DEBUGGING:
  44. public static String toString(int code)
  45. {
  46. switch (code)
  47. {
  48. case DO: return "DO";
  49. case DONT: return "DONT";
  50. case WILL: return "WILL";
  51. case WONT: return "WONT";
  52. case ECHO: return "ECHO";
  53. case LINEMODE: return "LINEMODE";
  54. case TTYPE: return "TTYPE";
  55. case NAWS: return "NAWS";
  56. case SUPPRESS_GO_AHEAD: return "SUPPRESS_GO_AHEAD";
  57. default: return Integer.toString(code);
  58. }
  59. }
  60. */
  61. public short windowHeight, windowWidth;
  62. public byte[] terminalType;
  63. final byte preferredLineMode = 3; // EDIT+TRAPSIG
  64. java.io.InputStream sin;
  65. java.io.OutputStream sout;
  66. TelnetInputStream in;
  67. TelnetOutputStream out;
  68. public TelnetInputStream getInputStream()
  69. {
  70. return in;
  71. }
  72. public TelnetOutputStream getOutputStream()
  73. {
  74. return out;
  75. }
  76. /** Used to store the the current state of negotiation of telnet options.
  77. * For example, for option LINEMODE (34), (telnet_options_state[34] & 7)
  78. * is the state of the option on this side, and
  79. * ((telnet_options_state[34] >> 3) & 7) is the state on the other side.
  80. * The 3 bits for each side can be any of OPTION_NO though OPTION_YES.
  81. * The option is only enabled if the value is OPTION_YES.
  82. * See RFC 1143. */
  83. final byte[] optionsState = new byte[256];
  84. /** The option is disabled, and no negotiating is in progress. */
  85. final static int OPTION_NO = 0;
  86. /** We sent out DONT/WONT and are waiting for confirming WONT/DONT. */
  87. final static int OPTION_WANTNO = 1;
  88. /** Like WANTNO, but we changed our mind. */
  89. final static int OPTION_WANTNO_OPPOSITE = 2;
  90. /** We sent out DO/WILL and are waiting for confirming WILL/DO. */
  91. final static int OPTION_WANTYES = 3;
  92. /** Like WANTYES, but we changed our mind. */
  93. final static int OPTION_WANTYES_OPPOSITE = 4;
  94. /** The option is enabled, and no negotiating is in progress. */
  95. final static int OPTION_YES = 5;
  96. /** Actually (try to) change the state for an option.
  97. * Return false is we don't know how or don't want to.
  98. * command is DO if we're enabling on this side;
  99. * DONT if we're disabling on this side;
  100. * WILL if we're enabling for the other side;
  101. * WONT if we're disabling for the other side.
  102. *
  103. * You should not call this function directly.
  104. * Instead, call request to send a request to the other side
  105. * (but with DO/WILL and DONT/WONT switched). Then, when
  106. * confirmation comes back, it is handled by the handle method, which
  107. * calls change.
  108. * The optionsState array may not have been updated yet.
  109. */
  110. boolean change (int command, int option)
  111. {
  112. if (option == TM)
  113. return true;
  114. if (isServer && option == NAWS)
  115. return true;
  116. if (isServer && command == WILL && option == LINEMODE)
  117. {
  118. byte[] buf = new byte[2];
  119. buf[0] = 1; // MODE
  120. buf[1] = preferredLineMode;
  121. try
  122. {
  123. out.writeSubCommand(LINEMODE, buf);
  124. }
  125. catch (java.io.IOException ex)
  126. {
  127. // Ignore it - I guess we'll do without.
  128. }
  129. return true;
  130. }
  131. if (isServer && command == WILL && option == TTYPE)
  132. {
  133. byte[] buf = new byte[1];
  134. buf[0] = 1; // Request SEND terminal-type.
  135. try
  136. {
  137. out.writeSubCommand(option, buf);
  138. }
  139. catch (java.io.IOException ex)
  140. {
  141. // Ignore it - I guess we'll do without.
  142. }
  143. return true;
  144. }
  145. if (! isServer && option == ECHO)
  146. {
  147. if (command == DO)
  148. return false;
  149. if (command == WILL)
  150. return true;
  151. }
  152. return false;
  153. }
  154. /** Handle a sub-command (SB-sequence) that we received. */
  155. public void subCommand (byte[] buf, int off, int len)
  156. {
  157. int command = buf[off];
  158. switch (command)
  159. {
  160. case NAWS:
  161. if (len == 5)
  162. {
  163. windowWidth = (short) ((buf[1] << 8) + (buf[2] & 0xFF));
  164. windowHeight = (short) ((buf[3] << 8) + (buf[4] & 0xFF));
  165. /*
  166. System.err.println("Window size: w:"
  167. +windowWidth+"*h:"+windowHeight);
  168. */
  169. return;
  170. }
  171. break;
  172. case TTYPE:
  173. byte[] type = new byte[len-1];
  174. System.arraycopy(buf, 1, type, 0, len-1);
  175. terminalType = type;
  176. System.err.println("terminal type: '"+new String(type)+"'");
  177. return;
  178. case LINEMODE:
  179. ///*
  180. System.err.println("SBCommand LINEMODE "+buf[1]+" len:"+len);
  181. if (buf[1] == 3) // SLC
  182. {
  183. for (int i = 2; i+2 < len; i += 3)
  184. {
  185. System.err.println(" "+buf[i]+","+buf[i+1]+","+buf[i+2]);
  186. }
  187. return;
  188. }
  189. //*/
  190. break;
  191. }
  192. }
  193. /** Handle a request from the other side.
  194. * Command is one of DO, DONT, WILL, WONT. */
  195. void handle (int command, int option) throws java.io.IOException
  196. {
  197. // True if the other side wants to change itself I.e. we got WILL/WONT);
  198. // false if it wants us to change (i.e. we got DO/DONT).
  199. boolean otherSide = command < DO;
  200. // True if DO or WILL; false if DONT or WONT.
  201. boolean wantOn = (command & 1) != 0;
  202. byte state = optionsState[option];
  203. // System.err.println("telnet handle "+toString(command)+", "+toString(option));
  204. if (otherSide)
  205. state >>= 3;
  206. switch ((state >> 3) & 7)
  207. {
  208. case OPTION_YES:
  209. if (wantOn)
  210. return; // Nothing to do.
  211. // Got a DONT or WONT. Disable without arguing.
  212. state = OPTION_NO;
  213. change(command, option);
  214. out.writeCommand(otherSide ? DONT : WONT, option);
  215. break;
  216. case OPTION_NO:
  217. if (! wantOn)
  218. return; // Nothing to do.
  219. if (change (command, option))
  220. {
  221. state = OPTION_YES;
  222. out.writeCommand(otherSide ? DO : WILL, option); // Confirm.
  223. }
  224. else
  225. {
  226. out.writeCommand(otherSide ? Telnet.DONT : Telnet.WONT,
  227. option);
  228. }
  229. break;
  230. case OPTION_WANTNO:
  231. state = OPTION_NO;
  232. break;
  233. case OPTION_WANTNO_OPPOSITE:
  234. // if (goalState) Error: DONT/WONT answered by WILL/DO.
  235. // Maybe some packets crossed in the mail.
  236. // Presumably the other side will take our original
  237. // request as de-conformation. In any case:
  238. state = OPTION_WANTYES;
  239. out.writeCommand(otherSide ? Telnet.DO : Telnet.WILL,
  240. option);
  241. break;
  242. case OPTION_WANTYES:
  243. if (wantOn)
  244. {
  245. state = OPTION_YES;
  246. change (command, option);
  247. }
  248. else
  249. state = OPTION_NO; // Declined.
  250. break;
  251. case OPTION_WANTYES_OPPOSITE:
  252. if (wantOn)
  253. {
  254. state = OPTION_WANTNO;
  255. out.writeCommand(otherSide ? DONT : WONT, option);
  256. }
  257. else
  258. {
  259. state = OPTION_NO;
  260. }
  261. break;
  262. }
  263. if (otherSide)
  264. state = (byte) ((optionsState[option] & 0xC7) | (state << 3));
  265. else
  266. state = (byte) ((optionsState[option] & 0xF8) | state);
  267. optionsState[option] = state;
  268. }
  269. /** Request (from this side) a new option state.
  270. * Command is one of DO, DONT, WILL, WONT. */
  271. public void request (int command, int option) throws java.io.IOException
  272. {
  273. // System.err.println("telnet request "+toString(command)+", "+toString(option));
  274. // True if we want other side to change,
  275. // false if we want this side to change.
  276. boolean otherSide = command >= DO;
  277. // True for DO, WILL; false for DONT or WONT.
  278. boolean wantOn = (command & 1) != 0;
  279. byte state = optionsState[option];
  280. if (otherSide)
  281. state >>= 3;
  282. switch (state & 7)
  283. {
  284. case OPTION_NO:
  285. if (wantOn)
  286. {
  287. state = OPTION_WANTYES;
  288. out.writeCommand(command, option);
  289. }
  290. // else: Redundant - already disabled.
  291. break;
  292. case OPTION_YES:
  293. if (! wantOn)
  294. {
  295. state = OPTION_WANTNO;
  296. out.writeCommand(command, option);
  297. }
  298. // else: Redundant - already enabled.
  299. break;
  300. case OPTION_WANTNO:
  301. if (wantOn)
  302. state = OPTION_WANTNO_OPPOSITE;
  303. // else: Error/redundant - already want to disable.
  304. break;
  305. case OPTION_WANTNO_OPPOSITE:
  306. if (! wantOn)
  307. state = OPTION_WANTNO;
  308. // else: Error/redundant - already want to enable
  309. break;
  310. case OPTION_WANTYES:
  311. if (! wantOn)
  312. state = OPTION_WANTYES_OPPOSITE;
  313. // else: Error/redundant - already want to disable.
  314. case OPTION_WANTYES_OPPOSITE:
  315. if (wantOn)
  316. state = OPTION_WANTYES;
  317. // else: Error/redundant - already want to enable
  318. break;
  319. }
  320. if (otherSide)
  321. state = (byte) ((optionsState[option] & 0xC7) | (state << 3));
  322. else
  323. state = (byte) ((optionsState[option] & 0xF8) | state);
  324. optionsState[option] = state;
  325. }
  326. static void usage ()
  327. {
  328. System.err.println("Usage: [java] kawa.Telnet HOST [PORT#]");
  329. System.exit(-1);
  330. }
  331. public static void main (String[] args)
  332. {
  333. if (args.length == 0)
  334. usage();
  335. String host = args[0];
  336. int port = 23;
  337. if (args.length > 1)
  338. {
  339. port = Integer.parseInt(args[1]);
  340. }
  341. try
  342. {
  343. java.net.Socket socket = new java.net.Socket(host, port);
  344. Telnet telnet = new Telnet(socket, false);
  345. TelnetOutputStream tout = telnet.getOutputStream();
  346. Thread t = new Thread(telnet);
  347. t.setPriority(Thread.currentThread().getPriority() + 1);
  348. t.start();
  349. byte[] buffer = new byte[1024];
  350. for (;;)
  351. {
  352. int ch = System.in.read();
  353. if (ch < 0)
  354. break; // send EOF FIXME
  355. buffer[0] = (byte) ch;
  356. int avail = System.in.available();
  357. if (avail > 0)
  358. {
  359. if (avail > buffer.length-1)
  360. avail = buffer.length - 1;
  361. avail = System.in.read(buffer, 1, avail);
  362. }
  363. tout.write(buffer, 0, avail+1);
  364. }
  365. t.stop();
  366. }
  367. catch (Exception ex)
  368. {
  369. System.err.println(ex);
  370. }
  371. }
  372. public Telnet (java.net.Socket socket, boolean isServer)
  373. throws java.io.IOException
  374. {
  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. public void run ()
  382. {
  383. try
  384. {
  385. TelnetInputStream tin = getInputStream();
  386. byte[] buffer = new byte[1024];
  387. for (;;)
  388. {
  389. int ch = tin.read();
  390. if (ch < 0)
  391. break; // ??? FIXME
  392. buffer[0] = (byte) ch;
  393. int avail = tin.available();
  394. if (avail > 0)
  395. {
  396. if (avail > buffer.length-1)
  397. avail = buffer.length - 1;
  398. avail = tin.read(buffer, 1, avail);
  399. }
  400. System.out.write(buffer, 0, avail+1);
  401. }
  402. }
  403. catch (java.io.IOException ex)
  404. {
  405. System.err.println(ex);
  406. System.exit(-1);
  407. }
  408. }
  409. }