1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.commons.net.telnet; 19 20 import java.io.BufferedInputStream; 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.io.OutputStream; 24 import java.time.Duration; 25 26 /** 27 * The TelnetClient class implements the simple network virtual terminal (NVT) for the Telnet protocol according to RFC 854. It does not implement any of the 28 * extra Telnet options because it is meant to be used within a Java program providing automated access to Telnet accessible resources. 29 * <p> 30 * The class can be used by first connecting to a server using the SocketClient {@link org.apache.commons.net.SocketClient#connect connect} method. Then an 31 * InputStream and OutputStream for sending and receiving data over the Telnet connection can be obtained by using the {@link #getInputStream getInputStream() } 32 * and {@link #getOutputStream getOutputStream() } methods. When you finish using the streams, you must call {@link #disconnect disconnect } rather than simply 33 * closing the streams. 34 * </p> 35 */ 36 public class TelnetClient extends Telnet { 37 private static final int DEFAULT_MAX_SUBNEGOTIATION_LENGTH = 512; 38 39 final int maxSubnegotiationLength; 40 private InputStream input; 41 private OutputStream output; 42 protected boolean readerThread = true; 43 private TelnetInputListener inputListener; 44 45 /** 46 * Default TelnetClient constructor, sets terminal-type {@code VT100}. 47 */ 48 public TelnetClient() { 49 this("VT100", DEFAULT_MAX_SUBNEGOTIATION_LENGTH); 50 } 51 52 /** 53 * Constructs an instance with the specified max subnegotiation length and the default terminal-type {@code VT100} 54 * 55 * @param maxSubnegotiationLength the size of the subnegotiation buffer 56 */ 57 public TelnetClient(final int maxSubnegotiationLength) { 58 this("VT100", maxSubnegotiationLength); 59 } 60 61 /** 62 * Constructs an instance with the specified terminal type. 63 * 64 * @param termtype the terminal type to use, e.g. {@code VT100} 65 */ 66 public TelnetClient(final String termtype) { 67 this(termtype, DEFAULT_MAX_SUBNEGOTIATION_LENGTH); 68 } 69 70 /** 71 * Constructs an instance with the specified terminal type and max subnegotiation length 72 * 73 * @param termtype the terminal type to use, e.g. {@code VT100} 74 * @param maxSubnegotiationLength the size of the subnegotiation buffer 75 */ 76 public TelnetClient(final String termtype, final int maxSubnegotiationLength) { 77 /* TERMINAL-TYPE option (start) */ 78 super(termtype); 79 /* TERMINAL-TYPE option (end) */ 80 this.input = null; 81 this.output = null; 82 this.maxSubnegotiationLength = maxSubnegotiationLength; 83 } 84 85 /** 86 * Handles special connection requirements. 87 * 88 * @throws IOException If an error occurs during connection setup. 89 */ 90 @Override 91 protected void _connectAction_() throws IOException { 92 super._connectAction_(); 93 final TelnetInputStream tmp = new TelnetInputStream(_input_, this, readerThread); 94 if (readerThread) { 95 tmp.start(); 96 } 97 // __input CANNOT refer to the TelnetInputStream. We run into 98 // blocking problems when some classes use TelnetInputStream, so 99 // we wrap it with a BufferedInputStream which we know is safe. 100 // This blocking behavior requires further investigation, but right 101 // now it looks like classes like InputStreamReader are not implemented 102 // in a safe manner. 103 input = new BufferedInputStream(tmp); 104 output = new TelnetOutputStream(this); 105 } 106 107 /** 108 * Registers a new TelnetOptionHandler for this telnet client to use. 109 * 110 * @param opthand - option handler to be registered. 111 * 112 * @throws InvalidTelnetOptionException on error 113 * @throws IOException on error 114 */ 115 @Override 116 public void addOptionHandler(final TelnetOptionHandler opthand) throws InvalidTelnetOptionException, IOException { 117 super.addOptionHandler(opthand); 118 } 119 /* open TelnetOptionHandler functionality (end) */ 120 121 void closeOutputStream() throws IOException { 122 if (_output_ == null) { 123 return; 124 } 125 try { 126 _output_.close(); 127 } finally { 128 _output_ = null; 129 } 130 } 131 132 /** 133 * Unregisters a TelnetOptionHandler. 134 * 135 * @param optcode - Code of the option to be unregistered. 136 * 137 * @throws InvalidTelnetOptionException on error 138 * @throws IOException on error 139 */ 140 @Override 141 public void deleteOptionHandler(final int optcode) throws InvalidTelnetOptionException, IOException { 142 super.deleteOptionHandler(optcode); 143 } 144 145 /** 146 * Disconnects the telnet session, closing the input and output streams as well as the socket. If you have references to the input and output streams of the 147 * telnet connection, you should not close them yourself, but rather call disconnect to properly close the connection. 148 */ 149 @Override 150 public void disconnect() throws IOException { 151 try { 152 if (input != null) { 153 input.close(); 154 } 155 if (output != null) { 156 output.close(); 157 } 158 } finally { // NET-594 159 output = null; 160 input = null; 161 super.disconnect(); 162 } 163 } 164 165 void flushOutputStream() throws IOException { 166 if (_output_ == null) { 167 throw new IOException("Stream closed"); 168 } 169 _output_.flush(); 170 } 171 172 /** 173 * Returns the telnet connection input stream. You should not close the stream when you finish with it. Rather, you should call {@link #disconnect 174 * disconnect }. 175 * 176 * @return The telnet connection input stream. 177 */ 178 public InputStream getInputStream() { 179 return input; 180 } 181 182 /** 183 * Returns the state of the option on the local side. 184 * 185 * @param option - Option to be checked. 186 * 187 * @return The state of the option on the local side. 188 */ 189 public boolean getLocalOptionState(final int option) { 190 /* BUG (option active when not already acknowledged) (start) */ 191 return stateIsWill(option) && requestedWill(option); 192 /* BUG (option active when not already acknowledged) (end) */ 193 } 194 195 /* Code Section added for supporting AYT (start) */ 196 197 /** 198 * Returns the telnet connection output stream. You should not close the stream when you finish with it. Rather, you should call {@link #disconnect 199 * disconnect }. 200 * 201 * @return The telnet connection output stream. 202 */ 203 public OutputStream getOutputStream() { 204 return output; 205 } 206 207 /** 208 * Gets the status of the reader thread. 209 * 210 * @return true if the reader thread is enabled, false otherwise 211 */ 212 public boolean getReaderThread() { 213 return readerThread; 214 } 215 216 /** 217 * Returns the state of the option on the remote side. 218 * 219 * @param option - Option to be checked. 220 * 221 * @return The state of the option on the remote side. 222 */ 223 public boolean getRemoteOptionState(final int option) { 224 /* BUG (option active when not already acknowledged) (start) */ 225 return stateIsDo(option) && requestedDo(option); 226 /* BUG (option active when not already acknowledged) (end) */ 227 } 228 /* open TelnetOptionHandler functionality (end) */ 229 230 /* open TelnetOptionHandler functionality (start) */ 231 232 // Notify input listener 233 void notifyInputListener() { 234 final TelnetInputListener listener; 235 synchronized (this) { 236 listener = this.inputListener; 237 } 238 if (listener != null) { 239 listener.telnetInputAvailable(); 240 } 241 } 242 243 /** 244 * Register a listener to be notified when new incoming data is available to be read on the {@link #getInputStream input stream}. Only one listener is 245 * supported at a time. 246 * 247 * <p> 248 * More precisely, notifications are issued whenever the number of bytes available for immediate reading (i.e., the value returned by 249 * {@link InputStream#available}) transitions from zero to non-zero. Note that (in general) multiple reads may be required to empty the buffer and reset 250 * this notification, because incoming bytes are being added to the internal buffer asynchronously. 251 * </p> 252 * 253 * <p> 254 * Notifications are only supported when a {@link #setReaderThread reader thread} is enabled for the connection. 255 * </p> 256 * 257 * @param listener listener to be registered; replaces any previous 258 * @since 3.0 259 */ 260 public synchronized void registerInputListener(final TelnetInputListener listener) { 261 this.inputListener = listener; 262 } 263 264 /** 265 * Registers a notification handler to which will be sent notifications of received telnet option negotiation commands. 266 * 267 * @param notifhand - TelnetNotificationHandler to be registered 268 */ 269 @Override 270 public void registerNotifHandler(final TelnetNotificationHandler notifhand) { 271 super.registerNotifHandler(notifhand); 272 } 273 274 /* Code Section added for supporting spystreams (start) */ 275 /** 276 * Registers an OutputStream for spying what's going on in the TelnetClient session. 277 * 278 * @param spystream - OutputStream on which session activity will be echoed. 279 */ 280 public void registerSpyStream(final OutputStream spystream) { 281 super._registerSpyStream(spystream); 282 } 283 284 /** 285 * Sends an {@code Are You There (AYT)} sequence and waits for the result. 286 * 287 * @param timeout - Time to wait for a response. 288 * 289 * @return true if AYT received a response, false otherwise. 290 * 291 * @throws InterruptedException on error 292 * @throws IllegalArgumentException on error 293 * @throws IOException on error 294 * @since 3.10.0 295 */ 296 public boolean sendAYT(final Duration timeout) throws IOException, IllegalArgumentException, InterruptedException { 297 return _sendAYT(timeout); 298 } 299 300 /** 301 * Sends an {@code Are You There (AYT)} sequence and waits for the result. 302 * 303 * @param timeout - Time to wait for a response (millis.) 304 * 305 * @return true if AYT received a response, false otherwise 306 * 307 * @throws InterruptedException on error 308 * @throws IllegalArgumentException on error 309 * @throws IOException on error 310 * @deprecated Use {@link #sendAYT(Duration)}. 311 */ 312 @Deprecated 313 public boolean sendAYT(final long timeout) throws IOException, IllegalArgumentException, InterruptedException { 314 return _sendAYT(Duration.ofMillis(timeout)); 315 } 316 317 /* Code Section added for supporting AYT (start) */ 318 319 /** 320 * Sends a command byte to the remote peer, adding the IAC prefix. 321 * 322 * <p> 323 * This method does not wait for any response. Messages sent by the remote end can be handled by registering an approrpriate {@link TelnetOptionHandler}. 324 * </p> 325 * 326 * @param command the code for the command 327 * @throws IOException if an I/O error occurs while writing the message 328 * @throws IllegalArgumentException on error 329 * @since 3.0 330 */ 331 public void sendCommand(final byte command) throws IOException, IllegalArgumentException { 332 _sendCommand(command); 333 } 334 335 /** 336 * Sends a protocol-specific subnegotiation message to the remote peer. {@link TelnetClient} will add the IAC SB & IAC SE framing bytes; the first byte 337 * in {@code message} should be the appropriate telnet option code. 338 * 339 * <p> 340 * This method does not wait for any response. Subnegotiation messages sent by the remote end can be handled by registering an approrpriate 341 * {@link TelnetOptionHandler}. 342 * </p> 343 * 344 * @param message option code followed by subnegotiation payload 345 * @throws IllegalArgumentException if {@code message} has length zero 346 * @throws IOException if an I/O error occurs while writing the message 347 * @since 3.0 348 */ 349 public void sendSubnegotiation(final int[] message) throws IOException, IllegalArgumentException { 350 if (message.length < 1) { 351 throw new IllegalArgumentException("zero length message"); 352 } 353 _sendSubnegotiation(message); 354 } 355 356 /** 357 * Sets the status of the reader thread. 358 * 359 * <p> 360 * When enabled, a seaparate internal reader thread is created for new connections to read incoming data as it arrives. This results in immediate handling 361 * of option negotiation, notifications, etc. (at least until the fixed-size internal buffer fills up). Otherwise, no thread is created an all negotiation 362 * and option handling is deferred until a read() is performed on the {@link #getInputStream input stream}. 363 * </p> 364 * 365 * <p> 366 * The reader thread must be enabled for {@link TelnetInputListener} support. 367 * </p> 368 * 369 * <p> 370 * When this method is invoked, the reader thread status will apply to all subsequent connections; the current connection (if any) is not affected. 371 * </p> 372 * 373 * @param flag true to enable the reader thread, false to disable 374 * @see #registerInputListener 375 */ 376 public void setReaderThread(final boolean flag) { 377 readerThread = flag; 378 } 379 380 /** 381 * Stops spying this TelnetClient. 382 */ 383 public void stopSpyStream() { 384 super._stopSpyStream(); 385 } 386 /* Code Section added for supporting spystreams (end) */ 387 388 /** 389 * Unregisters the current {@link TelnetInputListener}, if any. 390 * 391 * @since 3.0 392 */ 393 public synchronized void unregisterInputListener() { 394 this.inputListener = null; 395 } 396 397 /** 398 * Unregisters the current notification handler. 399 */ 400 @Override 401 public void unregisterNotifHandler() { 402 super.unregisterNotifHandler(); 403 } 404 }