2 * Jexer - Java Text User Interface
4 * License: LGPLv3 or later
6 * This module is licensed under the GNU Lesser General Public License
7 * Version 3. Please see the file "COPYING" in this directory for more
8 * information about the GNU Lesser General Public License Version 3.
10 * Copyright (C) 2015 Kevin Lamonte
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public License
14 * as published by the Free Software Foundation; either version 3 of
15 * the License, or (at your option) any later version.
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this program; if not, see
24 * http://www.gnu.org/licenses/, or write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
33 import java
.io
.InputStream
;
34 import java
.io
.IOException
;
36 import java
.util
.TreeMap
;
38 import jexer
.session
.SessionInfo
;
39 import static jexer
.net
.TelnetSocket
.*;
42 * TelnetInputStream works with TelnetSocket to perform the telnet protocol.
44 public final class TelnetInputStream
extends InputStream
implements SessionInfo
{
47 * The root TelnetSocket that has my telnet protocol state.
49 private TelnetSocket master
;
52 * The raw socket's InputStream.
54 private InputStream input
;
57 * Package private constructor.
59 * @param master the master TelnetSocket
60 * @param input the underlying socket's InputStream
62 TelnetInputStream(TelnetSocket master
, InputStream input
) {
67 // SessionInfo interface --------------------------------------------------
72 private String username
= "";
77 private String language
= "en_US";
82 private int windowWidth
= 80;
87 private int windowHeight
= 24;
92 * @return the username
94 public String
getUsername() {
101 * @param username the value
103 public void setUsername(final String username
) {
104 this.username
= username
;
110 * @return the language
112 public String
getLanguage() {
113 return this.language
;
119 * @param language the value
121 public void setLanguage(final String language
) {
122 this.language
= language
;
126 * Text window width getter.
128 * @return the window width
130 public int getWindowWidth() {
135 * Text window height getter.
137 * @return the window height
139 public int getWindowHeight() {
144 * Re-query the text window size.
146 public void queryWindowSize() {
150 // InputStream interface --------------------------------------------------
153 * Returns an estimate of the number of bytes that can be read (or
154 * skipped over) from this input stream without blocking by the next
155 * invocation of a method for this input stream.
159 public int available() throws IOException
{
165 * Closes this input stream and releases any system resources associated
169 public void close() throws IOException
{
174 * Marks the current position in this input stream.
177 public void mark(int readlimit
) {
182 * Tests if this input stream supports the mark and reset methods.
185 public boolean markSupported() {
190 * Reads the next byte of data from the input stream.
193 public int read() throws IOException
{
199 * Reads some number of bytes from the input stream and stores them into
200 * the buffer array b.
203 public int read(byte[] b
) throws IOException
{
209 * Reads up to len bytes of data from the input stream into an array of
213 public int read(byte[] b
, int off
, int len
) throws IOException
{
219 * Repositions this stream to the position at the time the mark method
220 * was last called on this input stream.
223 public void reset() throws IOException
{
228 * Skips over and discards n bytes of data from this input stream.
231 public long skip(long n
) throws IOException
{
237 // Telnet protocol --------------------------------------------------------
240 * The subnegotiation buffer.
242 private byte [] subnegBuffer
;
245 * For debugging, return a descriptive string for this telnet option.
246 * These are pulled from: http://www.iana.org/assignments/telnet-options
248 * @return a string describing the telnet option code
250 private String
optionString(final int option
) {
252 case 0: return "Binary Transmission";
253 case 1: return "Echo";
254 case 2: return "Reconnection";
255 case 3: return "Suppress Go Ahead";
256 case 4: return "Approx Message Size Negotiation";
257 case 5: return "Status";
258 case 6: return "Timing Mark";
259 case 7: return "Remote Controlled Trans and Echo";
260 case 8: return "Output Line Width";
261 case 9: return "Output Page Size";
262 case 10: return "Output Carriage-Return Disposition";
263 case 11: return "Output Horizontal Tab Stops";
264 case 12: return "Output Horizontal Tab Disposition";
265 case 13: return "Output Formfeed Disposition";
266 case 14: return "Output Vertical Tabstops";
267 case 15: return "Output Vertical Tab Disposition";
268 case 16: return "Output Linefeed Disposition";
269 case 17: return "Extended ASCII";
270 case 18: return "Logout";
271 case 19: return "Byte Macro";
272 case 20: return "Data Entry Terminal";
273 case 21: return "SUPDUP";
274 case 22: return "SUPDUP Output";
275 case 23: return "Send Location";
276 case 24: return "Terminal Type";
277 case 25: return "End of Record";
278 case 26: return "TACACS User Identification";
279 case 27: return "Output Marking";
280 case 28: return "Terminal Location Number";
281 case 29: return "Telnet 3270 Regime";
282 case 30: return "X.3 PAD";
283 case 31: return "Negotiate About Window Size";
284 case 32: return "Terminal Speed";
285 case 33: return "Remote Flow Control";
286 case 34: return "Linemode";
287 case 35: return "X Display Location";
288 case 36: return "Environment Option";
289 case 37: return "Authentication Option";
290 case 38: return "Encryption Option";
291 case 39: return "New Environment Option";
292 case 40: return "TN3270E";
293 case 41: return "XAUTH";
294 case 42: return "CHARSET";
295 case 43: return "Telnet Remote Serial Port (RSP)";
296 case 44: return "Com Port Control Option";
297 case 45: return "Telnet Suppress Local Echo";
298 case 46: return "Telnet Start TLS";
299 case 47: return "KERMIT";
300 case 48: return "SEND-URL";
301 case 49: return "FORWARD_X";
302 case 138: return "TELOPT PRAGMA LOGON";
303 case 139: return "TELOPT SSPI LOGON";
304 case 140: return "TELOPT PRAGMA HEARTBEAT";
305 case 255: return "Extended-Options-List";
307 if ((option
>= 50) && (option
<= 137)) {
310 return "UNKNOWN - OTHER";
315 * Send a DO/DON'T/WILL/WON'T response to the remote side.
317 * @param response a TELNET_DO/DONT/WILL/WONT byte
318 * @param option telnet option byte (binary mode, term type, etc.)
320 private void respond(final int response
,
321 final int option
) throws IOException
{
323 byte [] buffer
= new byte[3];
324 buffer
[0] = (byte)TELNET_IAC
;
325 buffer
[1] = (byte)response
;
326 buffer
[2] = (byte)option
;
328 master
.output
.write(buffer
);
332 * Tell the remote side we WILL support an option.
334 * @param option telnet option byte (binary mode, term type, etc.)
336 private void WILL(final int option
) throws IOException
{
337 respond(TELNET_WILL
, option
);
341 * Tell the remote side we WON'T support an option.
343 * @param option telnet option byte (binary mode, term type, etc.)
345 private void WONT(final int option
) throws IOException
{
346 respond(TELNET_WONT
, option
);
350 * Tell the remote side we DO support an option.
352 * @param option telnet option byte (binary mode, term type, etc.)
354 private void DO(final int option
) throws IOException
{
355 respond(TELNET_DO
, option
);
359 * Tell the remote side we DON'T support an option.
361 * @param option telnet option byte (binary mode, term type, etc.)
363 private void DONT(final int option
) throws IOException
{
364 respond(TELNET_DONT
, option
);
368 * Tell the remote side we WON't or DON'T support an option.
370 * @param remoteQuery a TELNET_DO/DONT/WILL/WONT byte
371 * @param option telnet option byte (binary mode, term type, etc.)
373 private void refuse(final int remoteQuery
,
374 final int option
) throws IOException
{
376 if (remoteQuery
== TELNET_DO
) {
384 * Build sub-negotiation packet (RFC 855)
386 * @param option telnet option
387 * @param response output buffer of response bytes
389 private void telnetSendSubnegResponse(final int option
,
390 final byte [] response
) throws IOException
{
392 byte [] buffer
= new byte[response
.length
+ 5];
393 buffer
[0] = (byte)TELNET_IAC
;
394 buffer
[1] = (byte)TELNET_SB
;
395 buffer
[2] = (byte)option
;
396 System
.arraycopy(response
, 0, buffer
, 3, response
.length
);
397 buffer
[response
.length
+ 3] = (byte)TELNET_IAC
;
398 buffer
[response
.length
+ 4] = (byte)TELNET_SE
;
399 master
.output
.write(buffer
);
403 * Telnet option: Terminal Speed (RFC 1079). Client side.
405 private void telnetSendTerminalSpeed() throws IOException
{
406 byte [] response
= {0, '3', '8', '4', '0', '0', ',',
407 '3', '8', '4', '0', '0'};
408 telnetSendSubnegResponse(32, response
);
412 * Telnet option: Terminal Type (RFC 1091). Client side.
414 private void telnetSendTerminalType() throws IOException
{
415 byte [] response
= {0, 'v', 't', '1', '0', '0' };
416 telnetSendSubnegResponse(24, response
);
420 * Telnet option: Terminal Type (RFC 1091). Server side.
422 private void requestTerminalType() throws IOException
{
423 byte [] response
= new byte[1];
425 telnetSendSubnegResponse(24, response
);
429 * Telnet option: Terminal Speed (RFC 1079). Server side.
431 private void requestTerminalSpeed() throws IOException
{
432 byte [] response
= new byte[1];
434 telnetSendSubnegResponse(32, response
);
438 * Telnet option: New Environment (RFC 1572). Server side.
440 private void requestEnvironment() throws IOException
{
441 byte [] response
= new byte[1];
443 telnetSendSubnegResponse(39, response
);
447 * Send the options we want to negotiate on:
448 * Binary Transmission RFC 856
449 * Suppress Go Ahead RFC 858
450 * Negotiate About Window Size RFC 1073
451 * Terminal Type RFC 1091
452 * Terminal Speed RFC 1079
453 * New Environment RFC 1572
455 * When run as a server:
458 private void telnetSendOptions() throws IOException
{
459 if (master
.nvt
.binaryMode
== false) {
460 // Binary Transmission: must ask both do and will
465 if (master
.nvt
.goAhead
== true) {
471 // Server only options
472 if (master
.nvt
.isServer
== true) {
473 // Enable Echo - I echo to them, they do not echo back to me.
477 if (master
.nvt
.doTermType
== true) {
478 // Terminal type - request it
482 if (master
.nvt
.doTermSpeed
== true) {
483 // Terminal speed - request it
487 if (master
.nvt
.doNAWS
== true) {
492 if (master
.nvt
.doEnvironment
== true) {
493 // Environment - request it
499 if (master
.nvt
.doTermType
== true) {
500 // Terminal type - request it
504 if (master
.nvt
.doTermSpeed
== true) {
505 // Terminal speed - request it
509 if (master
.nvt
.doNAWS
== true) {
514 if (master
.nvt
.doEnvironment
== true) {
515 // Environment - request it
523 * New Environment parsing state.
525 private enum EnvState
{
533 * Handle the New Environment option. Note that this implementation
534 * fails to handle ESC as defined in RFC 1572.
536 private void handleNewEnvironment() {
537 Map
<StringBuilder
, StringBuilder
> newEnv
= new TreeMap
<StringBuilder
, StringBuilder
>();
538 EnvState state
= EnvState
.INIT
;
539 StringBuilder name
= new StringBuilder();
540 StringBuilder value
= new StringBuilder();
542 for (int i
= 0; i
< subnegBuffer
.length
; i
++) {
543 byte b
= subnegBuffer
[i
];
550 state
= EnvState
.TYPE
;
552 // The other side isn't following the rules, see ya.
558 // Looking for "VAR" or "USERVAR"
561 state
= EnvState
.NAME
;
562 name
= new StringBuilder();
565 state
= EnvState
.NAME
;
566 name
= new StringBuilder();
568 // The other side isn't following the rules, see ya
574 // Looking for "VALUE" or a name byte
577 state
= EnvState
.VALUE
;
578 value
= new StringBuilder();
580 // Take it as an environment variable name/key byte
581 name
.append((char)b
);
587 // Looking for "VAR", "USERVAR", or a name byte, or the end
590 state
= EnvState
.NAME
;
591 if (value
.length() > 0) {
592 newEnv
.put(name
, value
);
594 name
= new StringBuilder();
597 state
= EnvState
.NAME
;
598 if (value
.length() > 0) {
599 newEnv
.put(name
, value
);
601 name
= new StringBuilder();
603 // Take it as an environment variable value byte
604 value
.append((char)b
);
610 if ((name
.length() > 0) && (value
.length() > 0)) {
611 newEnv
.put(name
, value
);
614 for (StringBuilder key
: newEnv
.keySet()) {
615 if (key
.equals("LANG")) {
616 language
= newEnv
.get(key
).toString();
618 if (key
.equals("LOGNAME")) {
619 username
= newEnv
.get(key
).toString();
621 if (key
.equals("USER")) {
622 username
= newEnv
.get(key
).toString();
628 * Handle an option sub-negotiation.
630 private void handleSubneg() throws IOException
{
633 // Sanity check: there must be at least 1 byte in subnegBuffer
634 if (subnegBuffer
.length
< 1) {
635 // Buffer too small: the other side is a broken telnetd, it did
636 // not send the right sub-negotiation data. Bail out now.
639 option
= subnegBuffer
[0];
645 if ((subnegBuffer
.length
> 1) && (subnegBuffer
[1] == 1)) {
646 // Server sent "SEND", we say "IS"
647 telnetSendTerminalType();
649 if ((subnegBuffer
.length
> 1) && (subnegBuffer
[1] == 0)) {
650 // Client sent "IS", record it
651 StringBuilder terminalString
= new StringBuilder();
652 for (int i
= 2; i
< subnegBuffer
.length
; i
++) {
653 terminalString
.append((char)subnegBuffer
[i
]);
655 master
.nvt
.terminal
= terminalString
.toString();
661 if ((subnegBuffer
.length
> 1) && (subnegBuffer
[1] == 1)) {
662 // Server sent "SEND", we say "IS"
663 telnetSendTerminalSpeed();
665 if ((subnegBuffer
.length
> 1) && (subnegBuffer
[1] == 0)) {
666 // Client sent "IS", record it
667 StringBuilder speedString
= new StringBuilder();
668 for (int i
= 2; i
< subnegBuffer
.length
; i
++) {
669 speedString
.append((char)subnegBuffer
[i
]);
671 String termSpeed
= speedString
.toString();
677 if (subnegBuffer
.length
>= 5) {
681 if (subnegBuffer
[i
] == TELNET_IAC
) {
684 windowWidth
= subnegBuffer
[i
] * 256;
687 if (subnegBuffer
[i
] == TELNET_IAC
) {
690 windowWidth
+= subnegBuffer
[i
];
693 if (subnegBuffer
[i
] == TELNET_IAC
) {
696 windowHeight
= subnegBuffer
[i
] * 256;
699 if (subnegBuffer
[i
] == TELNET_IAC
) {
702 windowHeight
+= subnegBuffer
[i
];
708 handleNewEnvironment();