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