telnet socket stubs
[nikiroo-utils.git] / src / jexer / net / TelnetInputStream.java
CommitLineData
ea91242c
KL
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 */
31package jexer.net;
32
33import java.io.InputStream;
34import java.io.IOException;
35import java.util.Map;
36import java.util.TreeMap;
37
38import jexer.session.SessionInfo;
39import static jexer.net.TelnetSocket.*;
40
41/**
42 * TelnetInputStream works with TelnetSocket to perform the telnet protocol.
43 */
44public 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 * Package private constructor.
58 *
59 * @param master the master TelnetSocket
60 * @param input the underlying socket's InputStream
61 */
62 TelnetInputStream(TelnetSocket master, InputStream input) {
63 this.master = master;
64 this.input = input;
65 }
66
67 // SessionInfo interface --------------------------------------------------
68
69 /**
70 * User name.
71 */
72 private String username = "";
73
74 /**
75 * Language.
76 */
77 private String language = "en_US";
78
79 /**
80 * Text window width.
81 */
82 private int windowWidth = 80;
83
84 /**
85 * Text window height.
86 */
87 private int windowHeight = 24;
88
89 /**
90 * Username getter.
91 *
92 * @return the username
93 */
94 public String getUsername() {
95 return this.username;
96 }
97
98 /**
99 * Username setter.
100 *
101 * @param username the value
102 */
103 public void setUsername(final String username) {
104 this.username = username;
105 }
106
107 /**
108 * Language getter.
109 *
110 * @return the language
111 */
112 public String getLanguage() {
113 return this.language;
114 }
115
116 /**
117 * Language setter.
118 *
119 * @param language the value
120 */
121 public void setLanguage(final String language) {
122 this.language = language;
123 }
124
125 /**
126 * Text window width getter.
127 *
128 * @return the window width
129 */
130 public int getWindowWidth() {
131 return windowWidth;
132 }
133
134 /**
135 * Text window height getter.
136 *
137 * @return the window height
138 */
139 public int getWindowHeight() {
140 return windowHeight;
141 }
142
143 /**
144 * Re-query the text window size.
145 */
146 public void queryWindowSize() {
147 // NOP
148 }
149
150 // InputStream interface --------------------------------------------------
151
152 /**
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.
156 *
157 */
158 @Override
159 public int available() throws IOException {
160 // TODO
161 return 0;
162 }
163
164 /**
165 * Closes this input stream and releases any system resources associated
166 * with the stream.
167 */
168 @Override
169 public void close() throws IOException {
170 // TODO
171 }
172
173 /**
174 * Marks the current position in this input stream.
175 */
176 @Override
177 public void mark(int readlimit) {
178 // TODO
179 }
180
181 /**
182 * Tests if this input stream supports the mark and reset methods.
183 */
184 @Override
185 public boolean markSupported() {
186 return false;
187 }
188
189 /**
190 * Reads the next byte of data from the input stream.
191 */
192 @Override
193 public int read() throws IOException {
194 // TODO
195 return -1;
196 }
197
198 /**
199 * Reads some number of bytes from the input stream and stores them into
200 * the buffer array b.
201 */
202 @Override
203 public int read(byte[] b) throws IOException {
204 // TODO
205 return -1;
206 }
207
208 /**
209 * Reads up to len bytes of data from the input stream into an array of
210 * bytes.
211 */
212 @Override
213 public int read(byte[] b, int off, int len) throws IOException {
214 // TODO
215 return -1;
216 }
217
218 /**
219 * Repositions this stream to the position at the time the mark method
220 * was last called on this input stream.
221 */
222 @Override
223 public void reset() throws IOException {
224 // TODO
225 }
226
227 /**
228 * Skips over and discards n bytes of data from this input stream.
229 */
230 @Override
231 public long skip(long n) throws IOException {
232 // TODO
233 return -1;
234 }
235
236
237 // Telnet protocol --------------------------------------------------------
238
239 /**
240 * The subnegotiation buffer.
241 */
242 private byte [] subnegBuffer;
243
244 /**
245 * For debugging, return a descriptive string for this telnet option.
246 * These are pulled from: http://www.iana.org/assignments/telnet-options
247 *
248 * @return a string describing the telnet option code
249 */
250 private String optionString(final int option) {
251 switch (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";
306 default:
307 if ((option >= 50) && (option <= 137)) {
308 return "Unassigned";
309 }
310 return "UNKNOWN - OTHER";
311 }
312 }
313
314 /**
315 * Send a DO/DON'T/WILL/WON'T response to the remote side.
316 *
317 * @param response a TELNET_DO/DONT/WILL/WONT byte
318 * @param option telnet option byte (binary mode, term type, etc.)
319 */
320 private void respond(final int response,
321 final int option) throws IOException {
322
323 byte [] buffer = new byte[3];
324 buffer[0] = (byte)TELNET_IAC;
325 buffer[1] = (byte)response;
326 buffer[2] = (byte)option;
327
328 master.output.write(buffer);
329 }
330
331 /**
332 * Tell the remote side we WILL support an option.
333 *
334 * @param option telnet option byte (binary mode, term type, etc.)
335 */
336 private void WILL(final int option) throws IOException {
337 respond(TELNET_WILL, option);
338 }
339
340 /**
341 * Tell the remote side we WON'T support an option.
342 *
343 * @param option telnet option byte (binary mode, term type, etc.)
344 */
345 private void WONT(final int option) throws IOException {
346 respond(TELNET_WONT, option);
347 }
348
349 /**
350 * Tell the remote side we DO support an option.
351 *
352 * @param option telnet option byte (binary mode, term type, etc.)
353 */
354 private void DO(final int option) throws IOException {
355 respond(TELNET_DO, option);
356 }
357
358 /**
359 * Tell the remote side we DON'T support an option.
360 *
361 * @param option telnet option byte (binary mode, term type, etc.)
362 */
363 private void DONT(final int option) throws IOException {
364 respond(TELNET_DONT, option);
365 }
366
367 /**
368 * Tell the remote side we WON't or DON'T support an option.
369 *
370 * @param remoteQuery a TELNET_DO/DONT/WILL/WONT byte
371 * @param option telnet option byte (binary mode, term type, etc.)
372 */
373 private void refuse(final int remoteQuery,
374 final int option) throws IOException {
375
376 if (remoteQuery == TELNET_DO) {
377 WONT(option);
378 } else {
379 DONT(option);
380 }
381 }
382
383 /**
384 * Build sub-negotiation packet (RFC 855)
385 *
386 * @param option telnet option
387 * @param response output buffer of response bytes
388 */
389 private void telnetSendSubnegResponse(final int option,
390 final byte [] response) throws IOException {
391
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);
400 }
401
402 /**
403 * Telnet option: Terminal Speed (RFC 1079). Client side.
404 */
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);
409 }
410
411 /**
412 * Telnet option: Terminal Type (RFC 1091). Client side.
413 */
414 private void telnetSendTerminalType() throws IOException {
415 byte [] response = {0, 'v', 't', '1', '0', '0' };
416 telnetSendSubnegResponse(24, response);
417 }
418
419 /**
420 * Telnet option: Terminal Type (RFC 1091). Server side.
421 */
422 private void requestTerminalType() throws IOException {
423 byte [] response = new byte[1];
424 response[0] = 1;
425 telnetSendSubnegResponse(24, response);
426 }
427
428 /**
429 * Telnet option: Terminal Speed (RFC 1079). Server side.
430 */
431 private void requestTerminalSpeed() throws IOException {
432 byte [] response = new byte[1];
433 response[0] = 1;
434 telnetSendSubnegResponse(32, response);
435 }
436
437 /**
438 * Telnet option: New Environment (RFC 1572). Server side.
439 */
440 private void requestEnvironment() throws IOException {
441 byte [] response = new byte[1];
442 response[0] = 1;
443 telnetSendSubnegResponse(39, response);
444 }
445
446 /**
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
454 *
455 * When run as a server:
456 * Echo
457 */
458 private void telnetSendOptions() throws IOException {
459 if (master.nvt.binaryMode == false) {
460 // Binary Transmission: must ask both do and will
461 DO(0);
462 WILL(0);
463 }
464
465 if (master.nvt.goAhead == true) {
466 // Suppress Go Ahead
467 DO(3);
468 WILL(3);
469 }
470
471 // Server only options
472 if (master.nvt.isServer == true) {
473 // Enable Echo - I echo to them, they do not echo back to me.
474 DONT(1);
475 WILL(1);
476
477 if (master.nvt.doTermType == true) {
478 // Terminal type - request it
479 DO(24);
480 }
481
482 if (master.nvt.doTermSpeed == true) {
483 // Terminal speed - request it
484 DO(32);
485 }
486
487 if (master.nvt.doNAWS == true) {
488 // NAWS - request it
489 DO(31);
490 }
491
492 if (master.nvt.doEnvironment == true) {
493 // Environment - request it
494 DO(39);
495 }
496
497 } else {
498
499 if (master.nvt.doTermType == true) {
500 // Terminal type - request it
501 WILL(24);
502 }
503
504 if (master.nvt.doTermSpeed == true) {
505 // Terminal speed - request it
506 WILL(32);
507 }
508
509 if (master.nvt.doNAWS == true) {
510 // NAWS - request it
511 WILL(31);
512 }
513
514 if (master.nvt.doEnvironment == true) {
515 // Environment - request it
516 WILL(39);
517 }
518
519 }
520 }
521
522 /**
523 * New Environment parsing state.
524 */
525 private enum EnvState {
526 INIT,
527 TYPE,
528 NAME,
529 VALUE
530 }
531
532 /**
533 * Handle the New Environment option. Note that this implementation
534 * fails to handle ESC as defined in RFC 1572.
535 */
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();
541
542 for (int i = 0; i < subnegBuffer.length; i++) {
543 byte b = subnegBuffer[i];
544
545 switch (state) {
546
547 case INIT:
548 // Looking for "IS"
549 if (b == 0) {
550 state = EnvState.TYPE;
551 } else {
552 // The other side isn't following the rules, see ya.
553 return;
554 }
555 break;
556
557 case TYPE:
558 // Looking for "VAR" or "USERVAR"
559 if (b == 0) {
560 // VAR
561 state = EnvState.NAME;
562 name = new StringBuilder();
563 } else if (b == 3) {
564 // USERVAR
565 state = EnvState.NAME;
566 name = new StringBuilder();
567 } else {
568 // The other side isn't following the rules, see ya
569 return;
570 }
571 break;
572
573 case NAME:
574 // Looking for "VALUE" or a name byte
575 if (b == 1) {
576 // VALUE
577 state = EnvState.VALUE;
578 value = new StringBuilder();
579 } else {
580 // Take it as an environment variable name/key byte
581 name.append((char)b);
582 }
583
584 break;
585
586 case VALUE:
587 // Looking for "VAR", "USERVAR", or a name byte, or the end
588 if (b == 0) {
589 // VAR
590 state = EnvState.NAME;
591 if (value.length() > 0) {
592 newEnv.put(name, value);
593 }
594 name = new StringBuilder();
595 } else if (b == 3) {
596 // USERVAR
597 state = EnvState.NAME;
598 if (value.length() > 0) {
599 newEnv.put(name, value);
600 }
601 name = new StringBuilder();
602 } else {
603 // Take it as an environment variable value byte
604 value.append((char)b);
605 }
606 break;
607 }
608 }
609
610 if ((name.length() > 0) && (value.length() > 0)) {
611 newEnv.put(name, value);
612 }
613
614 for (StringBuilder key: newEnv.keySet()) {
615 if (key.equals("LANG")) {
616 language = newEnv.get(key).toString();
617 }
618 if (key.equals("LOGNAME")) {
619 username = newEnv.get(key).toString();
620 }
621 if (key.equals("USER")) {
622 username = newEnv.get(key).toString();
623 }
624 }
625 }
626
627 /**
628 * Handle an option sub-negotiation.
629 */
630 private void handleSubneg() throws IOException {
631 byte option;
632
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.
637 return;
638 }
639 option = subnegBuffer[0];
640
641 switch (option) {
642
643 case 24:
644 // Terminal Type
645 if ((subnegBuffer.length > 1) && (subnegBuffer[1] == 1)) {
646 // Server sent "SEND", we say "IS"
647 telnetSendTerminalType();
648 }
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]);
654 }
655 master.nvt.terminal = terminalString.toString();
656 }
657 break;
658
659 case 32:
660 // Terminal Speed
661 if ((subnegBuffer.length > 1) && (subnegBuffer[1] == 1)) {
662 // Server sent "SEND", we say "IS"
663 telnetSendTerminalSpeed();
664 }
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]);
670 }
671 String termSpeed = speedString.toString();
672 }
673 break;
674
675 case 31:
676 // NAWS
677 if (subnegBuffer.length >= 5) {
678 int i = 0;
679
680 i++;
681 if (subnegBuffer[i] == TELNET_IAC) {
682 i++;
683 }
684 windowWidth = subnegBuffer[i] * 256;
685
686 i++;
687 if (subnegBuffer[i] == TELNET_IAC) {
688 i++;
689 }
690 windowWidth += subnegBuffer[i];
691
692 i++;
693 if (subnegBuffer[i] == TELNET_IAC) {
694 i++;
695 }
696 windowHeight = subnegBuffer[i] * 256;
697
698 i++;
699 if (subnegBuffer[i] == TELNET_IAC) {
700 i++;
701 }
702 windowHeight += subnegBuffer[i];
703 }
704 break;
705
706 case 39:
707 // Environment
708 handleNewEnvironment();
709 break;
710
711 default:
712 // Ignore this one
713 break;
714 }
715 }
716
717
718}