001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.net.telnet; 019 020import java.io.BufferedInputStream; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.OutputStream; 024import java.time.Duration; 025 026import org.apache.commons.io.IOUtils; 027 028/** 029 * 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 030 * extra Telnet options because it is meant to be used within a Java program providing automated access to Telnet accessible resources. 031 * <p> 032 * 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 033 * InputStream and OutputStream for sending and receiving data over the Telnet connection can be obtained by using the {@link #getInputStream getInputStream()} 034 * and {@link #getOutputStream getOutputStream()} methods. When you finish using the streams, you must call {@link #disconnect disconnect} rather than simply 035 * closing the streams. 036 * </p> 037 */ 038public class TelnetClient extends Telnet { 039 private static final int DEFAULT_MAX_SUBNEGOTIATION_LENGTH = 512; 040 041 /** 042 * the size of the subnegotiation buffer. 043 */ 044 final int maxSubnegotiationLength; 045 046 /** Input stream. */ 047 private InputStream input; 048 049 /** Output stream. */ 050 private OutputStream output; 051 052 /** 053 * Whether to enable the reader thread. 054 */ 055 protected boolean readerThread = true; 056 057 /** 058 * Telnet input listener. 059 */ 060 private TelnetInputListener inputListener; 061 062 /** 063 * Default TelnetClient constructor, sets terminal-type {@code VT100}. 064 */ 065 public TelnetClient() { 066 this("VT100", DEFAULT_MAX_SUBNEGOTIATION_LENGTH); 067 } 068 069 /** 070 * Constructs an instance with the specified max subnegotiation length and the default terminal-type {@code VT100}. 071 * 072 * @param maxSubnegotiationLength the size of the subnegotiation buffer. 073 */ 074 public TelnetClient(final int maxSubnegotiationLength) { 075 this("VT100", maxSubnegotiationLength); 076 } 077 078 /** 079 * Constructs an instance with the specified terminal type. 080 * 081 * @param termtype the terminal type to use, e.g. {@code VT100} 082 */ 083 public TelnetClient(final String termtype) { 084 this(termtype, DEFAULT_MAX_SUBNEGOTIATION_LENGTH); 085 } 086 087 /** 088 * Constructs an instance with the specified terminal type and max subnegotiation length 089 * 090 * @param termType the terminal type to use, e.g. {@code VT100} 091 * @param maxSubnegotiationLength the size of the subnegotiation buffer 092 */ 093 public TelnetClient(final String termType, final int maxSubnegotiationLength) { 094 /* TERMINAL-TYPE option (start) */ 095 super(termType); 096 /* TERMINAL-TYPE option (end) */ 097 this.input = null; 098 this.output = null; 099 this.maxSubnegotiationLength = maxSubnegotiationLength; 100 } 101 102 /** 103 * Handles special connection requirements. 104 * 105 * @throws IOException If an error occurs during connection setup. 106 */ 107 @Override 108 protected void _connectAction_() throws IOException { 109 super._connectAction_(); 110 final TelnetInputStream tmp = new TelnetInputStream(_input_, this, readerThread); 111 if (readerThread) { 112 tmp.start(); 113 } 114 // input CANNOT refer to the TelnetInputStream. We run into 115 // blocking problems when some classes use TelnetInputStream, so 116 // we wrap it with a BufferedInputStream which we know is safe. 117 // This blocking behavior requires further investigation, but right 118 // now it looks like classes like InputStreamReader are not implemented 119 // in a safe manner. 120 input = new BufferedInputStream(tmp); 121 output = new TelnetOutputStream(this); 122 } 123 124 /** 125 * Registers a new TelnetOptionHandler for this Telnet client to use. 126 * 127 * @param opthand option handler to be registered. 128 * @throws InvalidTelnetOptionException on error 129 * @throws IOException on error 130 */ 131 @Override 132 public void addOptionHandler(final TelnetOptionHandler opthand) throws InvalidTelnetOptionException, IOException { 133 super.addOptionHandler(opthand); 134 } 135 /* open TelnetOptionHandler functionality (end) */ 136 137 void closeOutputStream() throws IOException { 138 if (_output_ == null) { 139 return; 140 } 141 try { 142 _output_.close(); 143 } finally { 144 _output_ = null; 145 } 146 } 147 148 /** 149 * Unregisters a TelnetOptionHandler. 150 * 151 * @param optcode Code of the option to be unregistered. 152 * @throws InvalidTelnetOptionException on error 153 * @throws IOException on error 154 */ 155 @Override 156 public void deleteOptionHandler(final int optcode) throws InvalidTelnetOptionException, IOException { 157 super.deleteOptionHandler(optcode); 158 } 159 160 /** 161 * 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 162 * Telnet connection, you should not close them yourself, but rather call disconnect to properly close the connection. 163 */ 164 @Override 165 public void disconnect() throws IOException { 166 try { 167 IOUtils.close(input); 168 IOUtils.close(output); 169 } finally { // NET-594 170 output = null; 171 input = null; 172 super.disconnect(); 173 } 174 } 175 176 void flushOutputStream() throws IOException { 177 if (_output_ == null) { 178 throw new IOException("Stream closed"); 179 } 180 _output_.flush(); 181 } 182 183 /** 184 * Gets the Telnet connection input stream. You should not close the stream when you finish with it. Rather, you should call {@link #disconnect 185 * disconnect }. 186 * 187 * @return The Telnet connection input stream. 188 */ 189 public InputStream getInputStream() { 190 return input; 191 } 192 193 /** 194 * Gets the state of the option on the local side. 195 * 196 * @param option Option to be checked. 197 * @return The state of the option on the local side. 198 */ 199 public boolean getLocalOptionState(final int option) { 200 /* BUG (option active when not already acknowledged) (start) */ 201 return stateIsWill(option) && requestedWill(option); 202 /* BUG (option active when not already acknowledged) (end) */ 203 } 204 205 /* Code Section added for supporting AYT (start) */ 206 207 /** 208 * Gets the Telnet connection output stream. You should not close the stream when you finish with it. Rather, you should call {@link #disconnect 209 * disconnect }. 210 * 211 * @return The Telnet connection output stream. 212 */ 213 public OutputStream getOutputStream() { 214 return output; 215 } 216 217 /** 218 * Gets the status of the reader thread. 219 * 220 * @return true if the reader thread is enabled, false otherwise 221 */ 222 public boolean getReaderThread() { 223 return readerThread; 224 } 225 226 /** 227 * Gets the state of the option on the remote side. 228 * 229 * @param option Option to be checked. 230 * @return The state of the option on the remote side. 231 */ 232 public boolean getRemoteOptionState(final int option) { 233 /* BUG (option active when not already acknowledged) (start) */ 234 return stateIsDo(option) && requestedDo(option); 235 /* BUG (option active when not already acknowledged) (end) */ 236 } 237 /* open TelnetOptionHandler functionality (end) */ 238 239 /* open TelnetOptionHandler functionality (start) */ 240 241 // Notify input listener 242 void notifyInputListener() { 243 final TelnetInputListener listener; 244 synchronized (this) { 245 listener = this.inputListener; 246 } 247 if (listener != null) { 248 listener.telnetInputAvailable(); 249 } 250 } 251 252 /** 253 * 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 254 * supported at a time. 255 * 256 * <p> 257 * More precisely, notifications are issued whenever the number of bytes available for immediate reading (i.e., the value returned by 258 * {@link InputStream#available}) transitions from zero to non-zero. Note that (in general) multiple reads may be required to empty the buffer and reset 259 * this notification, because incoming bytes are being added to the internal buffer asynchronously. 260 * </p> 261 * 262 * <p> 263 * Notifications are only supported when a {@link #setReaderThread reader thread} is enabled for the connection. 264 * </p> 265 * 266 * @param listener listener to be registered, replaces any previous listener. 267 * @since 3.0 268 */ 269 public synchronized void registerInputListener(final TelnetInputListener listener) { 270 this.inputListener = listener; 271 } 272 273 /* Code Section added for supporting spystreams (start) */ 274 /** 275 * Registers an OutputStream for spying what's going on in the TelnetClient session. 276 * 277 * @param spystream OutputStream on which session activity will be echoed. 278 */ 279 public void registerSpyStream(final OutputStream spystream) { 280 super._registerSpyStream(spystream); 281 } 282 283 /** 284 * Sends an {@code Are You There (AYT)} sequence and waits for the result. 285 * 286 * @param timeout Time to wait for a response. 287 * @return true if AYT received a response, false otherwise. 288 * @throws InterruptedException on error 289 * @throws IllegalArgumentException on error 290 * @throws IOException on error 291 * @since 3.10.0 292 */ 293 public boolean sendAYT(final Duration timeout) throws IOException, IllegalArgumentException, InterruptedException { 294 return _sendAYT(timeout); 295 } 296 297 /** 298 * Sends an {@code Are You There (AYT)} sequence and waits for the result. 299 * 300 * @param timeout Time to wait for a response (millis.) 301 * @return true if AYT received a response, false otherwise 302 * @throws InterruptedException on error 303 * @throws IllegalArgumentException on error 304 * @throws IOException on error 305 * @deprecated Use {@link #sendAYT(Duration)}. 306 */ 307 @Deprecated 308 public boolean sendAYT(final long timeout) throws IOException, IllegalArgumentException, InterruptedException { 309 return _sendAYT(Duration.ofMillis(timeout)); 310 } 311 312 /* Code Section added for supporting AYT (start) */ 313 314 /** 315 * Sends a command byte to the remote peer, adding the IAC prefix. 316 * 317 * <p> 318 * This method does not wait for any response. Messages sent by the remote end can be handled by registering an approrpriate {@link TelnetOptionHandler}. 319 * </p> 320 * 321 * @param command the code for the command 322 * @throws IOException if an I/O error occurs while writing the message 323 * @throws IllegalArgumentException on error 324 * @since 3.0 325 */ 326 public void sendCommand(final byte command) throws IOException, IllegalArgumentException { 327 _sendCommand(command); 328 } 329 330 /** 331 * Sends a protocol-specific subnegotiation message to the remote peer. {@link TelnetClient} will add the IAC SB & IAC SE framing bytes; the first byte 332 * in {@code message} should be the appropriate Telnet option code. 333 * 334 * <p> 335 * This method does not wait for any response. Subnegotiation messages sent by the remote end can be handled by registering an approrpriate 336 * {@link TelnetOptionHandler}. 337 * </p> 338 * 339 * @param message option code followed by subnegotiation payload 340 * @throws IllegalArgumentException if {@code message} has length zero 341 * @throws IOException if an I/O error occurs while writing the message 342 * @since 3.0 343 */ 344 public void sendSubnegotiation(final int[] message) throws IOException, IllegalArgumentException { 345 if (message.length < 1) { 346 throw new IllegalArgumentException("zero length message"); 347 } 348 _sendSubnegotiation(message); 349 } 350 351 /** 352 * Sets the status of the reader thread. 353 * 354 * <p> 355 * When enabled, a seaparate internal reader thread is created for new connections to read incoming data as it arrives. This results in immediate handling 356 * of option negotiation, notifications, etc. (at least until the fixed-size internal buffer fills up). Otherwise, no thread is created an all negotiation 357 * and option handling is deferred until a read() is performed on the {@link #getInputStream input stream}. 358 * </p> 359 * 360 * <p> 361 * The reader thread must be enabled for {@link TelnetInputListener} support. 362 * </p> 363 * 364 * <p> 365 * When this method is invoked, the reader thread status will apply to all subsequent connections; the current connection (if any) is not affected. 366 * </p> 367 * 368 * @param readerThread true to enable the reader thread, false to disable. 369 * @see #registerInputListener 370 */ 371 public void setReaderThread(final boolean readerThread) { 372 this.readerThread = readerThread; 373 } 374 375 /** 376 * Stops spying this TelnetClient. 377 */ 378 public void stopSpyStream() { 379 super._stopSpyStream(); 380 } 381 /* Code Section added for supporting spystreams (end) */ 382 383 /** 384 * Unregisters the current {@link TelnetInputListener}, if any. 385 * 386 * @since 3.0 387 */ 388 public synchronized void unregisterInputListener() { 389 this.inputListener = null; 390 } 391 392}