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 * The telnet-aware OutputStream.
59 private TelnetOutputStream output
;
62 * Persistent read buffer. In practice this will only be used if the
63 * single-byte read() is called sometime.
65 private byte [] readBuffer
;
68 * Current writing position in readBuffer - what is passed into
71 private int readBufferEnd
;
74 * Current read position in readBuffer - what is passed to the client in
75 * response to this.read().
77 private int readBufferStart
;
80 * Package private constructor.
82 * @param master the master TelnetSocket
83 * @param input the underlying socket's InputStream
84 * @param output the telnet-aware OutputStream
86 TelnetInputStream(final TelnetSocket master
, final InputStream input
,
87 final TelnetOutputStream output
) {
93 // Setup new read buffer
94 readBuffer
= new byte[1024];
99 // SessionInfo interface --------------------------------------------------
104 private String username
= "";
109 private String language
= "en_US";
114 private int windowWidth
= 80;
117 * Text window height.
119 private int windowHeight
= 24;
124 * @return the username
126 public String
getUsername() {
127 return this.username
;
133 * @param username the value
135 public void setUsername(final String username
) {
136 this.username
= username
;
142 * @return the language
144 public String
getLanguage() {
145 return this.language
;
151 * @param language the value
153 public void setLanguage(final String language
) {
154 this.language
= language
;
158 * Text window width getter.
160 * @return the window width
162 public int getWindowWidth() {
167 * Text window height getter.
169 * @return the window height
171 public int getWindowHeight() {
176 * Re-query the text window size.
178 public void queryWindowSize() {
182 // InputStream interface --------------------------------------------------
185 * Returns an estimate of the number of bytes that can be read (or
186 * skipped over) from this input stream without blocking by the next
187 * invocation of a method for this input stream.
191 public int available() throws IOException
{
192 if (readBuffer
== null) {
193 throw new IOException("InputStream is closed");
195 if (readBufferEnd
- readBufferStart
> 0) {
196 return (readBufferEnd
- readBufferStart
);
198 return input
.available();
202 * Closes this input stream and releases any system resources associated
206 public void close() throws IOException
{
207 if (readBuffer
!= null) {
214 * Marks the current position in this input stream.
217 public void mark(int readlimit
) {
222 * Tests if this input stream supports the mark and reset methods.
225 public boolean markSupported() {
230 * Reads the next byte of data from the input stream.
233 public int read() throws IOException
{
235 // If the post-processed buffer has bytes, use that.
236 if (readBufferEnd
- readBufferStart
> 0) {
238 return readBuffer
[readBufferStart
- 1];
241 // The buffer is empty, so reset the indexes to 0.
245 // Read some fresh data and run it through the telnet protocol.
246 int rc
= readImpl(readBuffer
, readBufferEnd
,
247 readBuffer
.length
- readBufferEnd
);
249 // If we got something, return it.
252 return readBuffer
[readBufferStart
- 1];
254 // If we read 0, I screwed up big time.
262 * Reads some number of bytes from the input stream and stores them into
263 * the buffer array b.
266 public int read(byte[] b
) throws IOException
{
267 return read(b
, 0, b
.length
);
271 * Reads up to len bytes of data from the input stream into an array of
275 public int read(byte[] b
, int off
, int len
) throws IOException
{
276 // The only time we can return 0 is if len is 0, as per the
277 // InputStream contract.
282 // If the post-processed buffer has bytes, use that.
283 if (readBufferEnd
- readBufferStart
> 0) {
284 int n
= Math
.min(len
, readBufferEnd
- readBufferStart
);
285 System
.arraycopy(b
, off
, readBuffer
, readBufferStart
, n
);
286 readBufferStart
+= n
;
290 // The buffer is empty, so reset the indexes to 0.
294 // The maximum number of bytes we will ask for will definitely be
295 // within the bounds of what we can return in a single call.
296 int n
= Math
.min(len
, readBuffer
.length
);
298 // Read some fresh data and run it through the telnet protocol.
299 int rc
= readImpl(readBuffer
, readBufferEnd
, n
);
301 // If we got something, return it.
303 System
.arraycopy(readBuffer
, 0, b
, off
, len
);
306 // If we read 0, I screwed up big time.
314 * Repositions this stream to the position at the time the mark method
315 * was last called on this input stream.
318 public void reset() throws IOException
{
319 throw new IOException("InputStream does not support mark/reset");
323 * Skips over and discards n bytes of data from this input stream.
326 public long skip(long n
) throws IOException
{
330 for (int i
= 0; i
< n
; i
++) {
336 // Telnet protocol --------------------------------------------------------
339 * The subnegotiation buffer.
341 private byte [] subnegBuffer
;
344 * For debugging, return a descriptive string for this telnet option.
345 * These are pulled from: http://www.iana.org/assignments/telnet-options
347 * @return a string describing the telnet option code
349 private String
optionString(final int option
) {
351 case 0: return "Binary Transmission";
352 case 1: return "Echo";
353 case 2: return "Reconnection";
354 case 3: return "Suppress Go Ahead";
355 case 4: return "Approx Message Size Negotiation";
356 case 5: return "Status";
357 case 6: return "Timing Mark";
358 case 7: return "Remote Controlled Trans and Echo";
359 case 8: return "Output Line Width";
360 case 9: return "Output Page Size";
361 case 10: return "Output Carriage-Return Disposition";
362 case 11: return "Output Horizontal Tab Stops";
363 case 12: return "Output Horizontal Tab Disposition";
364 case 13: return "Output Formfeed Disposition";
365 case 14: return "Output Vertical Tabstops";
366 case 15: return "Output Vertical Tab Disposition";
367 case 16: return "Output Linefeed Disposition";
368 case 17: return "Extended ASCII";
369 case 18: return "Logout";
370 case 19: return "Byte Macro";
371 case 20: return "Data Entry Terminal";
372 case 21: return "SUPDUP";
373 case 22: return "SUPDUP Output";
374 case 23: return "Send Location";
375 case 24: return "Terminal Type";
376 case 25: return "End of Record";
377 case 26: return "TACACS User Identification";
378 case 27: return "Output Marking";
379 case 28: return "Terminal Location Number";
380 case 29: return "Telnet 3270 Regime";
381 case 30: return "X.3 PAD";
382 case 31: return "Negotiate About Window Size";
383 case 32: return "Terminal Speed";
384 case 33: return "Remote Flow Control";
385 case 34: return "Linemode";
386 case 35: return "X Display Location";
387 case 36: return "Environment Option";
388 case 37: return "Authentication Option";
389 case 38: return "Encryption Option";
390 case 39: return "New Environment Option";
391 case 40: return "TN3270E";
392 case 41: return "XAUTH";
393 case 42: return "CHARSET";
394 case 43: return "Telnet Remote Serial Port (RSP)";
395 case 44: return "Com Port Control Option";
396 case 45: return "Telnet Suppress Local Echo";
397 case 46: return "Telnet Start TLS";
398 case 47: return "KERMIT";
399 case 48: return "SEND-URL";
400 case 49: return "FORWARD_X";
401 case 138: return "TELOPT PRAGMA LOGON";
402 case 139: return "TELOPT SSPI LOGON";
403 case 140: return "TELOPT PRAGMA HEARTBEAT";
404 case 255: return "Extended-Options-List";
406 if ((option
>= 50) && (option
<= 137)) {
409 return "UNKNOWN - OTHER";
414 * Send a DO/DON'T/WILL/WON'T response to the remote side.
416 * @param response a TELNET_DO/DONT/WILL/WONT byte
417 * @param option telnet option byte (binary mode, term type, etc.)
419 private void respond(final int response
,
420 final int option
) throws IOException
{
422 byte [] buffer
= new byte[3];
423 buffer
[0] = (byte)TELNET_IAC
;
424 buffer
[1] = (byte)response
;
425 buffer
[2] = (byte)option
;
427 output
.rawWrite(buffer
);
431 * Tell the remote side we WILL support an option.
433 * @param option telnet option byte (binary mode, term type, etc.)
435 private void WILL(final int option
) throws IOException
{
436 respond(TELNET_WILL
, option
);
440 * Tell the remote side we WON'T support an option.
442 * @param option telnet option byte (binary mode, term type, etc.)
444 private void WONT(final int option
) throws IOException
{
445 respond(TELNET_WONT
, option
);
449 * Tell the remote side we DO support an option.
451 * @param option telnet option byte (binary mode, term type, etc.)
453 private void DO(final int option
) throws IOException
{
454 respond(TELNET_DO
, option
);
458 * Tell the remote side we DON'T support an option.
460 * @param option telnet option byte (binary mode, term type, etc.)
462 private void DONT(final int option
) throws IOException
{
463 respond(TELNET_DONT
, option
);
467 * Tell the remote side we WON't or DON'T support an option.
469 * @param remoteQuery a TELNET_DO/DONT/WILL/WONT byte
470 * @param option telnet option byte (binary mode, term type, etc.)
472 private void refuse(final int remoteQuery
,
473 final int option
) throws IOException
{
475 if (remoteQuery
== TELNET_DO
) {
483 * Build sub-negotiation packet (RFC 855)
485 * @param option telnet option
486 * @param response output buffer of response bytes
488 private void telnetSendSubnegResponse(final int option
,
489 final byte [] response
) throws IOException
{
491 byte [] buffer
= new byte[response
.length
+ 5];
492 buffer
[0] = (byte)TELNET_IAC
;
493 buffer
[1] = (byte)TELNET_SB
;
494 buffer
[2] = (byte)option
;
495 System
.arraycopy(response
, 0, buffer
, 3, response
.length
);
496 buffer
[response
.length
+ 3] = (byte)TELNET_IAC
;
497 buffer
[response
.length
+ 4] = (byte)TELNET_SE
;
498 output
.rawWrite(buffer
);
502 * Telnet option: Terminal Speed (RFC 1079). Client side.
504 private void telnetSendTerminalSpeed() throws IOException
{
505 byte [] response
= {0, '3', '8', '4', '0', '0', ',',
506 '3', '8', '4', '0', '0'};
507 telnetSendSubnegResponse(32, response
);
511 * Telnet option: Terminal Type (RFC 1091). Client side.
513 private void telnetSendTerminalType() throws IOException
{
514 byte [] response
= {0, 'v', 't', '1', '0', '0' };
515 telnetSendSubnegResponse(24, response
);
519 * Telnet option: Terminal Type (RFC 1091). Server side.
521 private void requestTerminalType() throws IOException
{
522 byte [] response
= new byte[1];
524 telnetSendSubnegResponse(24, response
);
528 * Telnet option: Terminal Speed (RFC 1079). Server side.
530 private void requestTerminalSpeed() throws IOException
{
531 byte [] response
= new byte[1];
533 telnetSendSubnegResponse(32, response
);
537 * Telnet option: New Environment (RFC 1572). Server side.
539 private void requestEnvironment() throws IOException
{
540 byte [] response
= new byte[1];
542 telnetSendSubnegResponse(39, response
);
546 * Send the options we want to negotiate on:
547 * Binary Transmission RFC 856
548 * Suppress Go Ahead RFC 858
549 * Negotiate About Window Size RFC 1073
550 * Terminal Type RFC 1091
551 * Terminal Speed RFC 1079
552 * New Environment RFC 1572
554 * When run as a server:
557 void telnetSendOptions() throws IOException
{
558 if (master
.nvt
.binaryMode
== false) {
559 // Binary Transmission: must ask both do and will
564 if (master
.nvt
.goAhead
== true) {
570 // Server only options
571 if (master
.nvt
.isServer
== true) {
572 // Enable Echo - I echo to them, they do not echo back to me.
576 if (master
.nvt
.doTermType
== true) {
577 // Terminal type - request it
581 if (master
.nvt
.doTermSpeed
== true) {
582 // Terminal speed - request it
586 if (master
.nvt
.doNAWS
== true) {
591 if (master
.nvt
.doEnvironment
== true) {
592 // Environment - request it
598 if (master
.nvt
.doTermType
== true) {
599 // Terminal type - request it
603 if (master
.nvt
.doTermSpeed
== true) {
604 // Terminal speed - request it
608 if (master
.nvt
.doNAWS
== true) {
613 if (master
.nvt
.doEnvironment
== true) {
614 // Environment - request it
622 * New Environment parsing state.
624 private enum EnvState
{
632 * Handle the New Environment option. Note that this implementation
633 * fails to handle ESC as defined in RFC 1572.
635 private void handleNewEnvironment() {
636 Map
<StringBuilder
, StringBuilder
> newEnv
= new TreeMap
<StringBuilder
, StringBuilder
>();
637 EnvState state
= EnvState
.INIT
;
638 StringBuilder name
= new StringBuilder();
639 StringBuilder value
= new StringBuilder();
641 for (int i
= 0; i
< subnegBuffer
.length
; i
++) {
642 byte b
= subnegBuffer
[i
];
649 state
= EnvState
.TYPE
;
651 // The other side isn't following the rules, see ya.
657 // Looking for "VAR" or "USERVAR"
660 state
= EnvState
.NAME
;
661 name
= new StringBuilder();
664 state
= EnvState
.NAME
;
665 name
= new StringBuilder();
667 // The other side isn't following the rules, see ya
673 // Looking for "VALUE" or a name byte
676 state
= EnvState
.VALUE
;
677 value
= new StringBuilder();
679 // Take it as an environment variable name/key byte
680 name
.append((char)b
);
686 // Looking for "VAR", "USERVAR", or a name byte, or the end
689 state
= EnvState
.NAME
;
690 if (value
.length() > 0) {
691 newEnv
.put(name
, value
);
693 name
= new StringBuilder();
696 state
= EnvState
.NAME
;
697 if (value
.length() > 0) {
698 newEnv
.put(name
, value
);
700 name
= new StringBuilder();
702 // Take it as an environment variable value byte
703 value
.append((char)b
);
709 if ((name
.length() > 0) && (value
.length() > 0)) {
710 newEnv
.put(name
, value
);
713 for (StringBuilder key
: newEnv
.keySet()) {
714 if (key
.equals("LANG")) {
715 language
= newEnv
.get(key
).toString();
717 if (key
.equals("LOGNAME")) {
718 username
= newEnv
.get(key
).toString();
720 if (key
.equals("USER")) {
721 username
= newEnv
.get(key
).toString();
727 * Handle an option sub-negotiation.
729 private void handleSubneg() throws IOException
{
732 // Sanity check: there must be at least 1 byte in subnegBuffer
733 if (subnegBuffer
.length
< 1) {
734 // Buffer too small: the other side is a broken telnetd, it did
735 // not send the right sub-negotiation data. Bail out now.
738 option
= subnegBuffer
[0];
744 if ((subnegBuffer
.length
> 1) && (subnegBuffer
[1] == 1)) {
745 // Server sent "SEND", we say "IS"
746 telnetSendTerminalType();
748 if ((subnegBuffer
.length
> 1) && (subnegBuffer
[1] == 0)) {
749 // Client sent "IS", record it
750 StringBuilder terminalString
= new StringBuilder();
751 for (int i
= 2; i
< subnegBuffer
.length
; i
++) {
752 terminalString
.append((char)subnegBuffer
[i
]);
754 master
.nvt
.terminal
= terminalString
.toString();
760 if ((subnegBuffer
.length
> 1) && (subnegBuffer
[1] == 1)) {
761 // Server sent "SEND", we say "IS"
762 telnetSendTerminalSpeed();
764 if ((subnegBuffer
.length
> 1) && (subnegBuffer
[1] == 0)) {
765 // Client sent "IS", record it
766 StringBuilder speedString
= new StringBuilder();
767 for (int i
= 2; i
< subnegBuffer
.length
; i
++) {
768 speedString
.append((char)subnegBuffer
[i
]);
770 String termSpeed
= speedString
.toString();
776 if (subnegBuffer
.length
>= 5) {
780 if (subnegBuffer
[i
] == TELNET_IAC
) {
783 windowWidth
= subnegBuffer
[i
] * 256;
786 if (subnegBuffer
[i
] == TELNET_IAC
) {
789 windowWidth
+= subnegBuffer
[i
];
792 if (subnegBuffer
[i
] == TELNET_IAC
) {
795 windowHeight
= subnegBuffer
[i
] * 256;
798 if (subnegBuffer
[i
] == TELNET_IAC
) {
801 windowHeight
+= subnegBuffer
[i
];
807 handleNewEnvironment();
817 * Reads up to len bytes of data from the input stream into an array of
820 private int readImpl(byte[] b
, int off
, int len
) throws IOException
{