f409b7a6d17084fe021a9cbeb4920f1a087f6d4e
[nikiroo-utils.git] / src / jexer / net / TelnetInputStream.java
1 /**
2 * Jexer - Java Text User Interface
3 *
4 * License: LGPLv3 or later
5 *
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.
9 *
10 * Copyright (C) 2015 Kevin Lamonte
11 *
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.
16 *
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.
21 *
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
26 * 02110-1301 USA
27 *
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 * @version 1
30 */
31 package jexer.net;
32
33 import java.io.InputStream;
34 import java.io.IOException;
35 import java.util.Map;
36 import java.util.TreeMap;
37
38 import jexer.session.SessionInfo;
39 import static jexer.net.TelnetSocket.*;
40
41 /**
42 * TelnetInputStream works with TelnetSocket to perform the telnet protocol.
43 */
44 public final class TelnetInputStream extends InputStream implements SessionInfo {
45
46 /**
47 * The root TelnetSocket that has my telnet protocol state.
48 */
49 private TelnetSocket master;
50
51 /**
52 * The raw socket's InputStream.
53 */
54 private InputStream input;
55
56 /**
57 * The telnet-aware OutputStream.
58 */
59 private TelnetOutputStream output;
60
61 /**
62 * Persistent read buffer. In practice this will only be used if the
63 * single-byte read() is called sometime.
64 */
65 private byte [] readBuffer;
66
67 /**
68 * Current writing position in readBuffer - what is passed into
69 * input.read().
70 */
71 private int readBufferEnd;
72
73 /**
74 * Current read position in readBuffer - what is passed to the client in
75 * response to this.read().
76 */
77 private int readBufferStart;
78
79 /**
80 * Package private constructor.
81 *
82 * @param master the master TelnetSocket
83 * @param input the underlying socket's InputStream
84 * @param output the telnet-aware OutputStream
85 */
86 TelnetInputStream(final TelnetSocket master, final InputStream input,
87 final TelnetOutputStream output) {
88
89 this.master = master;
90 this.input = input;
91 this.output = output;
92
93 // Setup new read buffer
94 readBuffer = new byte[1024];
95 readBufferStart = 0;
96 readBufferEnd = 0;
97 }
98
99 // SessionInfo interface --------------------------------------------------
100
101 /**
102 * User name.
103 */
104 private String username = "";
105
106 /**
107 * Language.
108 */
109 private String language = "en_US";
110
111 /**
112 * Text window width.
113 */
114 private int windowWidth = 80;
115
116 /**
117 * Text window height.
118 */
119 private int windowHeight = 24;
120
121 /**
122 * Username getter.
123 *
124 * @return the username
125 */
126 public String getUsername() {
127 return this.username;
128 }
129
130 /**
131 * Username setter.
132 *
133 * @param username the value
134 */
135 public void setUsername(final String username) {
136 this.username = username;
137 }
138
139 /**
140 * Language getter.
141 *
142 * @return the language
143 */
144 public String getLanguage() {
145 return this.language;
146 }
147
148 /**
149 * Language setter.
150 *
151 * @param language the value
152 */
153 public void setLanguage(final String language) {
154 this.language = language;
155 }
156
157 /**
158 * Text window width getter.
159 *
160 * @return the window width
161 */
162 public int getWindowWidth() {
163 return windowWidth;
164 }
165
166 /**
167 * Text window height getter.
168 *
169 * @return the window height
170 */
171 public int getWindowHeight() {
172 return windowHeight;
173 }
174
175 /**
176 * Re-query the text window size.
177 */
178 public void queryWindowSize() {
179 // NOP
180 }
181
182 // InputStream interface --------------------------------------------------
183
184 /**
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.
188 *
189 */
190 @Override
191 public int available() throws IOException {
192 if (readBuffer == null) {
193 throw new IOException("InputStream is closed");
194 }
195 if (readBufferEnd - readBufferStart > 0) {
196 return (readBufferEnd - readBufferStart);
197 }
198 return input.available();
199 }
200
201 /**
202 * Closes this input stream and releases any system resources associated
203 * with the stream.
204 */
205 @Override
206 public void close() throws IOException {
207 if (readBuffer != null) {
208 readBuffer = null;
209 input.close();
210 }
211 }
212
213 /**
214 * Marks the current position in this input stream.
215 */
216 @Override
217 public void mark(int readlimit) {
218 // Do nothing
219 }
220
221 /**
222 * Tests if this input stream supports the mark and reset methods.
223 */
224 @Override
225 public boolean markSupported() {
226 return false;
227 }
228
229 /**
230 * Reads the next byte of data from the input stream.
231 */
232 @Override
233 public int read() throws IOException {
234
235 // If the post-processed buffer has bytes, use that.
236 if (readBufferEnd - readBufferStart > 0) {
237 readBufferStart++;
238 return readBuffer[readBufferStart - 1];
239 }
240
241 // The buffer is empty, so reset the indexes to 0.
242 readBufferStart = 0;
243 readBufferEnd = 0;
244
245 // Read some fresh data and run it through the telnet protocol.
246 int rc = readImpl(readBuffer, readBufferEnd,
247 readBuffer.length - readBufferEnd);
248
249 // If we got something, return it.
250 if (rc > 0) {
251 readBufferStart++;
252 return readBuffer[readBufferStart - 1];
253 }
254 // If we read 0, I screwed up big time.
255 assert (rc != 0);
256
257 // We read -1 (EOF).
258 return rc;
259 }
260
261 /**
262 * Reads some number of bytes from the input stream and stores them into
263 * the buffer array b.
264 */
265 @Override
266 public int read(byte[] b) throws IOException {
267 return read(b, 0, b.length);
268 }
269
270 /**
271 * Reads up to len bytes of data from the input stream into an array of
272 * bytes.
273 */
274 @Override
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.
278 if (len == 0) {
279 return 0;
280 }
281
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;
287 return n;
288 }
289
290 // The buffer is empty, so reset the indexes to 0.
291 readBufferStart = 0;
292 readBufferEnd = 0;
293
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);
297
298 // Read some fresh data and run it through the telnet protocol.
299 int rc = readImpl(readBuffer, readBufferEnd, n);
300
301 // If we got something, return it.
302 if (rc > 0) {
303 System.arraycopy(readBuffer, 0, b, off, len);
304 return rc;
305 }
306 // If we read 0, I screwed up big time.
307 assert (rc != 0);
308
309 // We read -1 (EOF).
310 return rc;
311 }
312
313 /**
314 * Repositions this stream to the position at the time the mark method
315 * was last called on this input stream.
316 */
317 @Override
318 public void reset() throws IOException {
319 throw new IOException("InputStream does not support mark/reset");
320 }
321
322 /**
323 * Skips over and discards n bytes of data from this input stream.
324 */
325 @Override
326 public long skip(long n) throws IOException {
327 if (n < 0) {
328 return 0;
329 }
330 for (int i = 0; i < n; i++) {
331 read();
332 }
333 return n;
334 }
335
336 // Telnet protocol --------------------------------------------------------
337
338 /**
339 * The subnegotiation buffer.
340 */
341 private byte [] subnegBuffer;
342
343 /**
344 * For debugging, return a descriptive string for this telnet option.
345 * These are pulled from: http://www.iana.org/assignments/telnet-options
346 *
347 * @return a string describing the telnet option code
348 */
349 private String optionString(final int option) {
350 switch (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";
405 default:
406 if ((option >= 50) && (option <= 137)) {
407 return "Unassigned";
408 }
409 return "UNKNOWN - OTHER";
410 }
411 }
412
413 /**
414 * Send a DO/DON'T/WILL/WON'T response to the remote side.
415 *
416 * @param response a TELNET_DO/DONT/WILL/WONT byte
417 * @param option telnet option byte (binary mode, term type, etc.)
418 */
419 private void respond(final int response,
420 final int option) throws IOException {
421
422 byte [] buffer = new byte[3];
423 buffer[0] = (byte)TELNET_IAC;
424 buffer[1] = (byte)response;
425 buffer[2] = (byte)option;
426
427 output.rawWrite(buffer);
428 }
429
430 /**
431 * Tell the remote side we WILL support an option.
432 *
433 * @param option telnet option byte (binary mode, term type, etc.)
434 */
435 private void WILL(final int option) throws IOException {
436 respond(TELNET_WILL, option);
437 }
438
439 /**
440 * Tell the remote side we WON'T support an option.
441 *
442 * @param option telnet option byte (binary mode, term type, etc.)
443 */
444 private void WONT(final int option) throws IOException {
445 respond(TELNET_WONT, option);
446 }
447
448 /**
449 * Tell the remote side we DO support an option.
450 *
451 * @param option telnet option byte (binary mode, term type, etc.)
452 */
453 private void DO(final int option) throws IOException {
454 respond(TELNET_DO, option);
455 }
456
457 /**
458 * Tell the remote side we DON'T support an option.
459 *
460 * @param option telnet option byte (binary mode, term type, etc.)
461 */
462 private void DONT(final int option) throws IOException {
463 respond(TELNET_DONT, option);
464 }
465
466 /**
467 * Tell the remote side we WON't or DON'T support an option.
468 *
469 * @param remoteQuery a TELNET_DO/DONT/WILL/WONT byte
470 * @param option telnet option byte (binary mode, term type, etc.)
471 */
472 private void refuse(final int remoteQuery,
473 final int option) throws IOException {
474
475 if (remoteQuery == TELNET_DO) {
476 WONT(option);
477 } else {
478 DONT(option);
479 }
480 }
481
482 /**
483 * Build sub-negotiation packet (RFC 855)
484 *
485 * @param option telnet option
486 * @param response output buffer of response bytes
487 */
488 private void telnetSendSubnegResponse(final int option,
489 final byte [] response) throws IOException {
490
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);
499 }
500
501 /**
502 * Telnet option: Terminal Speed (RFC 1079). Client side.
503 */
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);
508 }
509
510 /**
511 * Telnet option: Terminal Type (RFC 1091). Client side.
512 */
513 private void telnetSendTerminalType() throws IOException {
514 byte [] response = {0, 'v', 't', '1', '0', '0' };
515 telnetSendSubnegResponse(24, response);
516 }
517
518 /**
519 * Telnet option: Terminal Type (RFC 1091). Server side.
520 */
521 private void requestTerminalType() throws IOException {
522 byte [] response = new byte[1];
523 response[0] = 1;
524 telnetSendSubnegResponse(24, response);
525 }
526
527 /**
528 * Telnet option: Terminal Speed (RFC 1079). Server side.
529 */
530 private void requestTerminalSpeed() throws IOException {
531 byte [] response = new byte[1];
532 response[0] = 1;
533 telnetSendSubnegResponse(32, response);
534 }
535
536 /**
537 * Telnet option: New Environment (RFC 1572). Server side.
538 */
539 private void requestEnvironment() throws IOException {
540 byte [] response = new byte[1];
541 response[0] = 1;
542 telnetSendSubnegResponse(39, response);
543 }
544
545 /**
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
553 *
554 * When run as a server:
555 * Echo
556 */
557 void telnetSendOptions() throws IOException {
558 if (master.nvt.binaryMode == false) {
559 // Binary Transmission: must ask both do and will
560 DO(0);
561 WILL(0);
562 }
563
564 if (master.nvt.goAhead == true) {
565 // Suppress Go Ahead
566 DO(3);
567 WILL(3);
568 }
569
570 // Server only options
571 if (master.nvt.isServer == true) {
572 // Enable Echo - I echo to them, they do not echo back to me.
573 DONT(1);
574 WILL(1);
575
576 if (master.nvt.doTermType == true) {
577 // Terminal type - request it
578 DO(24);
579 }
580
581 if (master.nvt.doTermSpeed == true) {
582 // Terminal speed - request it
583 DO(32);
584 }
585
586 if (master.nvt.doNAWS == true) {
587 // NAWS - request it
588 DO(31);
589 }
590
591 if (master.nvt.doEnvironment == true) {
592 // Environment - request it
593 DO(39);
594 }
595
596 } else {
597
598 if (master.nvt.doTermType == true) {
599 // Terminal type - request it
600 WILL(24);
601 }
602
603 if (master.nvt.doTermSpeed == true) {
604 // Terminal speed - request it
605 WILL(32);
606 }
607
608 if (master.nvt.doNAWS == true) {
609 // NAWS - request it
610 WILL(31);
611 }
612
613 if (master.nvt.doEnvironment == true) {
614 // Environment - request it
615 WILL(39);
616 }
617
618 }
619 }
620
621 /**
622 * New Environment parsing state.
623 */
624 private enum EnvState {
625 INIT,
626 TYPE,
627 NAME,
628 VALUE
629 }
630
631 /**
632 * Handle the New Environment option. Note that this implementation
633 * fails to handle ESC as defined in RFC 1572.
634 */
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();
640
641 for (int i = 0; i < subnegBuffer.length; i++) {
642 byte b = subnegBuffer[i];
643
644 switch (state) {
645
646 case INIT:
647 // Looking for "IS"
648 if (b == 0) {
649 state = EnvState.TYPE;
650 } else {
651 // The other side isn't following the rules, see ya.
652 return;
653 }
654 break;
655
656 case TYPE:
657 // Looking for "VAR" or "USERVAR"
658 if (b == 0) {
659 // VAR
660 state = EnvState.NAME;
661 name = new StringBuilder();
662 } else if (b == 3) {
663 // USERVAR
664 state = EnvState.NAME;
665 name = new StringBuilder();
666 } else {
667 // The other side isn't following the rules, see ya
668 return;
669 }
670 break;
671
672 case NAME:
673 // Looking for "VALUE" or a name byte
674 if (b == 1) {
675 // VALUE
676 state = EnvState.VALUE;
677 value = new StringBuilder();
678 } else {
679 // Take it as an environment variable name/key byte
680 name.append((char)b);
681 }
682
683 break;
684
685 case VALUE:
686 // Looking for "VAR", "USERVAR", or a name byte, or the end
687 if (b == 0) {
688 // VAR
689 state = EnvState.NAME;
690 if (value.length() > 0) {
691 newEnv.put(name, value);
692 }
693 name = new StringBuilder();
694 } else if (b == 3) {
695 // USERVAR
696 state = EnvState.NAME;
697 if (value.length() > 0) {
698 newEnv.put(name, value);
699 }
700 name = new StringBuilder();
701 } else {
702 // Take it as an environment variable value byte
703 value.append((char)b);
704 }
705 break;
706 }
707 }
708
709 if ((name.length() > 0) && (value.length() > 0)) {
710 newEnv.put(name, value);
711 }
712
713 for (StringBuilder key: newEnv.keySet()) {
714 if (key.equals("LANG")) {
715 language = newEnv.get(key).toString();
716 }
717 if (key.equals("LOGNAME")) {
718 username = newEnv.get(key).toString();
719 }
720 if (key.equals("USER")) {
721 username = newEnv.get(key).toString();
722 }
723 }
724 }
725
726 /**
727 * Handle an option sub-negotiation.
728 */
729 private void handleSubneg() throws IOException {
730 byte option;
731
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.
736 return;
737 }
738 option = subnegBuffer[0];
739
740 switch (option) {
741
742 case 24:
743 // Terminal Type
744 if ((subnegBuffer.length > 1) && (subnegBuffer[1] == 1)) {
745 // Server sent "SEND", we say "IS"
746 telnetSendTerminalType();
747 }
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]);
753 }
754 master.nvt.terminal = terminalString.toString();
755 }
756 break;
757
758 case 32:
759 // Terminal Speed
760 if ((subnegBuffer.length > 1) && (subnegBuffer[1] == 1)) {
761 // Server sent "SEND", we say "IS"
762 telnetSendTerminalSpeed();
763 }
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]);
769 }
770 String termSpeed = speedString.toString();
771 }
772 break;
773
774 case 31:
775 // NAWS
776 if (subnegBuffer.length >= 5) {
777 int i = 0;
778
779 i++;
780 if (subnegBuffer[i] == TELNET_IAC) {
781 i++;
782 }
783 windowWidth = subnegBuffer[i] * 256;
784
785 i++;
786 if (subnegBuffer[i] == TELNET_IAC) {
787 i++;
788 }
789 windowWidth += subnegBuffer[i];
790
791 i++;
792 if (subnegBuffer[i] == TELNET_IAC) {
793 i++;
794 }
795 windowHeight = subnegBuffer[i] * 256;
796
797 i++;
798 if (subnegBuffer[i] == TELNET_IAC) {
799 i++;
800 }
801 windowHeight += subnegBuffer[i];
802 }
803 break;
804
805 case 39:
806 // Environment
807 handleNewEnvironment();
808 break;
809
810 default:
811 // Ignore this one
812 break;
813 }
814 }
815
816 /**
817 * Reads up to len bytes of data from the input stream into an array of
818 * bytes.
819 */
820 private int readImpl(byte[] b, int off, int len) throws IOException {
821 assert (len > 0);
822 // TODO
823 return -1;
824 }
825
826
827 }