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