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