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