1 /*
2 * SSHTools - Java SSH2 API
3 *
4 * Copyright (C) 2002-2003 Lee David Painter and Contributors.
5 *
6 * Contributions made by:
7 *
8 * Brett Smith
9 * Richard Pernavas
10 * Erwin Bolwidt
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 */
26 package com.sshtools.j2ssh;
27
28 import com.sshtools.j2ssh.authentication.AuthenticationProtocolClient;
29 import com.sshtools.j2ssh.authentication.AuthenticationProtocolState;
30 import com.sshtools.j2ssh.authentication.PublicKeyAuthenticationClient;
31 import com.sshtools.j2ssh.authentication.SshAuthenticationClient;
32 import com.sshtools.j2ssh.configuration.SshConnectionProperties;
33 import com.sshtools.j2ssh.connection.Channel;
34 import com.sshtools.j2ssh.connection.ChannelEventAdapter;
35 import com.sshtools.j2ssh.connection.ChannelEventListener;
36 import com.sshtools.j2ssh.connection.ChannelFactory;
37 import com.sshtools.j2ssh.connection.ConnectionProtocol;
38 import com.sshtools.j2ssh.forwarding.ForwardingClient;
39 import com.sshtools.j2ssh.net.TransportProvider;
40 import com.sshtools.j2ssh.net.TransportProviderFactory;
41 import com.sshtools.j2ssh.session.SessionChannelClient;
42 import com.sshtools.j2ssh.sftp.SftpSubsystemClient;
43 import com.sshtools.j2ssh.transport.ConsoleKnownHostsKeyVerification;
44 import com.sshtools.j2ssh.transport.HostKeyVerification;
45 import com.sshtools.j2ssh.transport.TransportProtocolClient;
46 import com.sshtools.j2ssh.transport.TransportProtocolState;
47 import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
48 import com.sshtools.j2ssh.util.State;
49
50 import org.apache.commons.logging.Log;
51 import org.apache.commons.logging.LogFactory;
52
53 import java.io.File;
54 import java.io.IOException;
55
56 import java.net.UnknownHostException;
57
58 import java.util.Iterator;
59 import java.util.List;
60 import java.util.Vector;
61
62
63 /**
64 * <p>
65 * Implements an SSH client with methods to connect to a remote server and
66 * perform all necersary SSH functions such as SCP, SFTP, executing commands,
67 * starting the users shell and perform port forwarding.
68 * </p>
69 *
70 * <p>
71 * There are several steps to perform prior to performing the desired task.
72 * This involves the making the initial connection, authenticating the user
73 * and creating a session to execute a command, shell or subsystem and/or
74 * configuring the port forwarding manager.
75 * </p>
76 *
77 * <p>
78 * To create a connection use the following code:<br>
79 * <blockquote><pre>
80 * // Create a instance and connect SshClient
81 * ssh = new SshClient();
82 * ssh.connect("hostname");
83 * </pre></blockquote>
84 * Once this code has executed and returned
85 * the connection is ready for authentication:<br>
86 * <blockquote><pre>
87 * PasswordAuthenticationClient pwd = new PasswordAuthenticationClient();
88 * pwd.setUsername("foo");
89 * pwd.setPassword("xxxxx");
90 * // Authenticate the user
91 * int result = ssh.authenticate(pwd);
92 * if(result==AuthenticationProtocolState.COMPLETED) {
93 * // Authentication complete
94 * }
95 * </pre></blockquote>
96 * Once authenticated the user's shell can be started:<br>
97 * <blockquote><pre>
98 * // Open a session channel
99 * SessionChannelClient session =
100 * ssh.openSessionChannel();
101 *
102 * // Request a pseudo terminal, if you do not you may not see the prompt
103 * if(session.requestPseudoTerminal("ansi", 80, 24, 0, 0, "") {
104 * // Start the users shell
105 * if(session.startShell()) {
106 * // Do something with the session output
107 * session.getOutputStream().write("echo message\n");
108 * ....
109 * }
110 * }
111 * </pre></blockquote>
112 * </p>
113 *
114 * @author Lee David Painter
115 * @version $Revision: 1.75 $
116 *
117 * @since 0.2.0
118 */
119 public class SshClient {
120 private static Log log = LogFactory.getLog(SshClient.class);
121
122 /**
123 * The SSH Authentication protocol implementation for this SSH client. The
124 * SSH Authentication protocol runs over the SSH Transport protocol as a
125 * transport protocol service.
126 */
127 protected AuthenticationProtocolClient authentication;
128
129 /**
130 * The SSH Connection protocol implementation for this SSH client. The
131 * connection protocol runs over the SSH Transport protocol as a transport
132 * protocol service and is started by the authentication protocol after a
133 * successful authentication.
134 */
135 protected ConnectionProtocol connection;
136
137 /** Provides a high level management interface for SSH port forwarding. */
138 protected ForwardingClient forwarding;
139
140 /** The SSH Transport protocol implementation for this SSH Client. */
141 protected TransportProtocolClient transport;
142
143 /** The current state of the authentication for the current connection. */
144 protected int authenticationState = AuthenticationProtocolState.READY;
145
146 /**
147 * The timeout in milliseconds for the underlying transport provider
148 * (typically a Socket).
149 */
150 protected int socketTimeout = 0;
151
152 /**
153 * A Transport protocol event handler instance that receives notifications
154 * of transport layer events such as Socket timeouts and disconnection.
155 */
156 protected SshEventAdapter eventHandler = null;
157
158 /** The currently active channels for this SSH Client connection. */
159 protected Vector activeChannels = new Vector();
160
161 /**
162 * An channel event listener implemention to maintain the active channel
163 * list.
164 */
165 protected ActiveChannelEventListener activeChannelListener = new ActiveChannelEventListener();
166
167 /**
168 * Flag indicating whether the forwarding instance is created when the
169 * connection is made.
170 */
171 protected boolean useDefaultForwarding = true;
172
173 /** The currently active Sftp clients */
174 private Vector activeSftpClients = new Vector();
175
176 /**
177 * <p>
178 * Contructs an unitilialized SshClient ready for connecting.
179 * </p>
180 */
181 public SshClient() {
182 }
183
184 /**
185 * <p>
186 * Returns the server's authentication banner.
187 * </p>
188 *
189 * <p>
190 * In some jurisdictions, sending a warning message before authentication
191 * may be relevant for getting legal protection. Many UNIX machines, for
192 * example, normally display text from `/etc/issue', or use "tcp wrappers"
193 * or similar software to display a banner before issuing a login prompt.
194 * </p>
195 *
196 * <p>
197 * The server may or may not send this message. Call this method to
198 * retrieve the message, specifying a timeout limit to wait for the
199 * message.
200 * </p>
201 *
202 * @param timeout The number of milliseconds to wait for the banner message
203 * before returning
204 *
205 * @return The server's banner message
206 *
207 * @exception IOException If an IO error occurs reading the message
208 *
209 * @since 0.2.0
210 */
211 public String getAuthenticationBanner(int timeout)
212 throws IOException {
213 if (authentication == null) {
214 return "";
215 } else {
216 return authentication.getBannerMessage(timeout);
217 }
218 }
219
220 /**
221 * <p>
222 * Returns the list of available authentication methods for a given user.
223 * </p>
224 *
225 * <p>
226 * A client may request a list of authentication methods that may continue
227 * by using the "none" authentication method.This method calls the "none"
228 * method and returns the available authentication methods.
229 * </p>
230 *
231 * @param username The name of the account for which you require the
232 * available authentication methods
233 *
234 * @return A list of Strings, for example "password", "publickey" &
235 * "keyboard-interactive"
236 *
237 * @exception IOException If an IO error occurs during the operation
238 *
239 * @since 0.2.0
240 */
241 public List getAvailableAuthMethods(String username)
242 throws IOException {
243 if (authentication != null) {
244 return authentication.getAvailableAuths(username,
245 connection.getServiceName());
246 } else {
247 return null;
248 }
249 }
250
251 /**
252 * <p>
253 * Returns the connection state of the client.
254 * </p>
255 *
256 * @return true if the client is connected, false otherwise
257 *
258 * @since 0.2.0
259 */
260 public boolean isConnected() {
261 State state = (transport == null) ? null : transport.getState();
262 int value = (state == null) ? TransportProtocolState.DISCONNECTED
263 : state.getValue();
264
265 return ((value == TransportProtocolState.CONNECTED) ||
266 (value == TransportProtocolState.PERFORMING_KEYEXCHANGE));
267 }
268
269 /**
270 * <p>
271 * Evaluate whether the client has successfully authenticated.
272 * </p>
273 *
274 * @return true if the client is authenticated, otherwise false
275 */
276 public boolean isAuthenticated() {
277 return authenticationState == AuthenticationProtocolState.COMPLETE;
278 }
279
280 /**
281 * <p>
282 * Returns the identification string sent by the server during protocol
283 * negotiation. For example "SSH-2.0-OpenSSH_p3.4".
284 * </p>
285 *
286 * @return The server's identification string.
287 *
288 * @since 0.2.0
289 */
290 public String getServerId() {
291 return transport.getRemoteId();
292 }
293
294 /**
295 * <p>
296 * Returns the server's public key supplied during key exchange.
297 * </p>
298 *
299 * @return the server's public key
300 *
301 * @since 0.2.0
302 */
303 public SshPublicKey getServerHostKey() {
304 return transport.getServerHostKey();
305 }
306
307 /**
308 * <p>
309 * Returns the transport protocol's connection state.
310 * </p>
311 *
312 * @return The transport protocol's state
313 *
314 * @since 0.2.0
315 */
316 public TransportProtocolState getConnectionState() {
317 return transport.getState();
318 }
319
320 /**
321 * <p>
322 * Returns the default port forwarding manager.
323 * </p>
324 *
325 * @return This connection's forwarding client
326 *
327 * @since 0.2.0
328 */
329 public ForwardingClient getForwardingClient() {
330 return forwarding;
331 }
332
333 /**
334 * <p>
335 * Return's a rough guess at the server's EOL setting. This is simply
336 * derived from the identification string and should not be used as a cast
337 * iron proof on the EOL setting.
338 * </p>
339 *
340 * @return The transport protocol's EOL constant
341 *
342 * @since 0.2.0
343 */
344 public int getRemoteEOL() {
345 return transport.getRemoteEOL();
346 }
347
348 /**
349 * <p>
350 * Set the event handler for the underlying transport protocol.
351 * </p>
352 * <blockquote>
353 * <pre>
354 * ssh.setEventHandler(new TransportProtocolEventHandler() {
355 *
356 * public void onSocketTimeout(TransportProtocol transport) {<br>
357 * // Do something to handle the socket timeout<br>
358 * }
359 *
360 * public void onDisconnect(TransportProtocol transport) {
361 * // Perhaps some clean up?
362 * }
363 * });
364 * </pre>
365 * </blockquote>
366 *
367 * @param eventHandler The event handler instance to receive transport
368 * protocol events
369 *
370 * @see com.sshtools.j2ssh.transport.TransportProtocolEventHandler
371 * @since 0.2.0
372 */
373 public void addEventHandler(SshEventAdapter eventHandler) {
374 // If were connected then add, otherwise store for later connection
375 if (transport != null) {
376 transport.addEventHandler(eventHandler);
377 authentication.addEventListener(eventHandler);
378 } else {
379 this.eventHandler = eventHandler;
380 }
381 }
382
383 /**
384 * <p>
385 * Set's the socket timeout (in milliseconds) for the underlying transport
386 * provider. This MUST be called prior to connect.
387 * </p>
388 * <blockquote>
389 * SshClient ssh = new SshClient();
390 * ssh.setSocketTimeout(30000);
391 * ssh.connect("hostname");
392 * </blockquote>
393 *
394 * @param milliseconds The number of milliseconds without activity before
395 * the timeout event occurs
396 *
397 * @since 0.2.0
398 */
399 public void setSocketTimeout(int milliseconds) {
400 this.socketTimeout = milliseconds;
401 }
402
403 /**
404 * <p>
405 * Return's a rough guess at the server's EOL setting. This is simply
406 * derived from the identification string and should not be used as a cast
407 * iron proof on the EOL setting.
408 * </p>
409 *
410 * @return The EOL string
411 *
412 * @since 0.2.0
413 */
414 public String getRemoteEOLString() {
415 return ((transport.getRemoteEOL() == TransportProtocolClient.EOL_CRLF)
416 ? "\r\n" : "\n");
417 }
418
419 /**
420 * Get the connection properties for this connection.
421 *
422 * @return
423 */
424 public SshConnectionProperties getConnectionProperties() {
425 return transport.getProperties();
426 }
427
428 /**
429 * <p>
430 * Authenticate the user on the remote host.
431 * </p>
432 *
433 * <p>
434 * To authenticate the user, create an <code>SshAuthenticationClient</code>
435 * instance and configure it with the authentication details.
436 * </p>
437 * <code> PasswordAuthenticationClient pwd = new
438 * PasswordAuthenticationClient(); pwd.setUsername("root");
439 * pwd.setPassword("xxxxxxxxx"); int result = ssh.authenticate(pwd);
440 * </code>
441 *
442 * <p>
443 * The method returns a result value will one of the public static values
444 * defined in <code>AuthenticationProtocolState</code>. These are<br>
445 * <br>
446 * COMPLETED - The authentication succeeded.<br>
447 * PARTIAL - The authentication succeeded but a further authentication
448 * method is required.<br>
449 * FAILED - The authentication failed.<br>
450 * CANCELLED - The user cancelled authentication (can only be returned
451 * when the user is prompted for information.<br>
452 * </p>
453 *
454 * @param auth A configured SshAuthenticationClient instance ready for
455 * authentication
456 *
457 * @return The authentication result
458 *
459 * @exception IOException If an IO error occurs during authentication
460 *
461 * @since 0.2.0
462 */
463 public int authenticate(SshAuthenticationClient auth)
464 throws IOException {
465 // Do the authentication
466 authenticationState = authentication.authenticate(auth, connection);
467
468 if ((authenticationState == AuthenticationProtocolState.COMPLETE) &&
469 useDefaultForwarding) {
470 // Use some method to synchronize forwardings on the ForwardingClient
471 forwarding.synchronizeConfiguration(transport.getProperties());
472 }
473
474 return authenticationState;
475 }
476
477 /**
478 * <p>
479 * Determine whether a private/public key pair will be accepted for public
480 * key authentication.
481 * </p>
482 *
483 * <p>
484 * When using public key authentication, the signing of data could take
485 * some time depending upon the available machine resources. By calling
486 * this method, you can determine whether the server will accept a key for
487 * authentication by providing the public key. The server will verify the
488 * key against the user's authorized keys and return true should the
489 * public key be authorized. The caller can then proceed with the private
490 * key operation.
491 * </p>
492 *
493 * @param username The username for authentication
494 * @param key The public key for which authentication will be attempted
495 *
496 * @return true if the server will accept the key, otherwise false
497 *
498 * @exception IOException If an IO error occurs during the operation
499 * @throws SshException
500 *
501 * @since 0.2.0
502 */
503 public boolean acceptsKey(String username, SshPublicKey key)
504 throws IOException {
505 if (authenticationState != AuthenticationProtocolState.COMPLETE) {
506 PublicKeyAuthenticationClient pk = new PublicKeyAuthenticationClient();
507
508 return pk.acceptsKey(authentication, username,
509 connection.getServiceName(), key);
510 } else {
511 throw new SshException("Authentication has been completed!");
512 }
513 }
514
515 /**
516 * <p>
517 * Connect the client to the server using default connection properties.
518 * </p>
519 *
520 * <p>
521 * This call attempts to connect to the hostname specified on the standard
522 * SSH port of 22 and uses all the default connection properties. This
523 * call is the equivilent of calling:
524 * </p>
525 * <blockquote><pre>
526 * SshConnectionProperties properties = new
527 * SshConnectionProperties();
528 * properties.setHostname("hostname");
529 * ssh.connect(properties);
530 * </pre></blockquote>
531 *
532 * @param hostname The hostname of the server to connect
533 *
534 * @exception IOException If an IO error occurs during the connect
535 * operation
536 *
537 * @see #connect(com.sshtools.j2ssh.configuration.SshConnectionProperties)
538 * @since 0.2.0
539 */
540 public void connect(String hostname) throws IOException {
541 connect(hostname, 22, new ConsoleKnownHostsKeyVerification());
542 }
543
544 /**
545 * <p>
546 * Connect the client to the server using the default connection
547 * properties.
548 * </p>
549 *
550 * <p>
551 * This call attempts to connect to the hostname specified on the standard
552 * SSH port of 22 and uses all the default connection properties. When
553 * this method returns the connection has been established, the server's
554 * identity been verified and the connection is ready for user
555 * authentication. Host key verification will be performed using the host
556 * key verification instance provided:
557 * </p>
558 * <blockquote><pre>
559 * // Connect and consult $HOME/.ssh/known_hosts
560 * ssh.connect("hostname", new ConsoleKnownHostsKeyVerification());
561 * // Connect and allow any host
562 * ssh.connect("hostname", new
563 * IgnoreHostKeyVerification());
564 * </pre></blockquote>
565 *
566 * <p>
567 * You can provide your own host key verification process by implementing
568 * the <code>HostKeyVerification</code> interface.
569 * </p>
570 *
571 * @param hostname The hostname of the server to connect
572 * @param hosts The host key verification instance to consult for host key
573 * validation
574 *
575 * @exception IOException If an IO error occurs during the connect
576 * operation
577 *
578 * @see #connect(com.sshtools.j2ssh.configuration.SshConnectionProperties,
579 * com.sshtools.j2ssh.transport.HostKeyVerification)
580 * @since 0.2.0
581 */
582 public void connect(String hostname, HostKeyVerification hosts)
583 throws IOException {
584 connect(hostname, 22, hosts);
585 }
586
587 /**
588 * <p>
589 * Connect the client to the server on a specified port with default
590 * connection properties.
591 * </p>
592 *
593 * <p>
594 * This call attempts to connect to the hostname and port specified. This
595 * call is the equivilent of calling:
596 * </p>
597 * <blockquote></pre>
598 * SshConnectionProperties properties = new
599 * SshConnectionProperties();
600 * properties.setHostname("hostname");
601 * properties.setPort(10022);
602 * ssh.connect(properties);
603 * </pre></blockquote>
604 *
605 * @param hostname The hostname of the server to connect
606 * @param port The port to connect
607 *
608 * @exception IOException If an IO error occurs during the connect
609 * operation
610 *
611 * @see #connect(com.sshtools.j2ssh.configuration.SshConnectionProperties)
612 * @since 0.2.0
613 */
614 public void connect(String hostname, int port) throws IOException {
615 connect(hostname, port, new ConsoleKnownHostsKeyVerification());
616 }
617
618 /**
619 * <p>
620 * Connect the client to the server on a specified port with default
621 * connection properties.
622 * </p>
623 *
624 * <p>
625 * This call attempts to connect to the hostname and port specified. When
626 * this method returns the connection has been established, the server's
627 * identity been verified and the connection is ready for user
628 * authentication. Host key verification will be performed using the host
629 * key verification instance provided:
630 * </p>
631 * <blockquote><pre>
632 * // Connect and consult $HOME/.ssh/known_hosts
633 * ssh.connect("hostname", new ConsoleKnownHostsKeyVerification());
634 * // Connect and allow any host
635 * ssh.connect("hostname", new
636 * IgnoreHostKeyVerification());
637 * </pre></blockquote>
638 *
639 * <p>
640 * You can provide your own host key verification process by implementing
641 * the <code>HostKeyVerification</code> interface.
642 * </p>
643 *
644 * @param hostname The hostname of the server to connect
645 * @param port The port to connect
646 * @param hosts The host key verification instance to consult for host key
647 * validation
648 *
649 * @exception IOException If an IO error occurs during the connect
650 * operation
651 *
652 * @see #connect(com.sshtools.j2ssh.configuration.SshConnectionProperties,
653 * com.sshtools.j2ssh.transport.HostKeyVerification)
654 * @since 0.2.0
655 */
656 public void connect(String hostname, int port, HostKeyVerification hosts)
657 throws IOException {
658 SshConnectionProperties properties = new SshConnectionProperties();
659 properties.setHost(hostname);
660 properties.setPort(port);
661 connect(properties, hosts);
662 }
663
664 /**
665 * <p>
666 * Connect the client to the server with the specified properties.
667 * </p>
668 *
669 * <p>
670 * This call attempts to connect to using the connection properties
671 * specified. When this method returns the connection has been
672 * established, the server's identity been verified and the connection is
673 * ready for user authentication. To use this method first create a
674 * properties instance and set the required fields.
675 * </p>
676 * <blockquote><pre>
677 * SshConnectionProperties properties = new
678 * SshConnectionProperties();
679 * properties.setHostname("hostname");
680 * properties.setPort(10022);
681 * properties.setPrefCSEncryption("blowfish-cbc");
682 * ssh.connect(properties);
683 * </pre></blockquote>
684 *
685 * <p>
686 * Host key verification will be performed using
687 * <code>ConsoleKnownHostsKeyVerification</code> and so this call is the
688 * equivilent of calling:
689 * </p>
690 * <blockquote><pre>
691 * ssh.connect("hostname", new ConsoleKnownHostsKeyVerification());
692 * </pre></blockquote>
693 *
694 * <p>
695 * If the key is not matched to any keys already in the
696 * $HOME/.ssh/known_hosts file, the user will be prompted via the console
697 * to confirm the identity of the remote server. The user will receive the
698 * following prompt.
699 * </p>
700 * <code> The host shell.sourceforge.net is currently unknown to the system
701 * The host key fingerprint is: 1024: 4c 68 3 d4 5c 58 a6 1d 9d 17 13 24
702 * 14 48 ba 99 Do you want to allow this host key? [Yes|No|Always]:
703 * </code>
704 *
705 * <p>
706 * Selecting the "always" option will write the key to the known_hosts
707 * file.
708 * </p>
709 *
710 * @param properties The connection properties
711 *
712 * @exception IOException If an IO error occurs during the connect
713 * operation
714 *
715 * @since 0.2.0
716 */
717 public void connect(SshConnectionProperties properties)
718 throws IOException {
719 connect(properties, new ConsoleKnownHostsKeyVerification());
720 }
721
722 /**
723 * <p>
724 * Connect the client to the server with the specified properties.
725 * </p>
726 *
727 * <p>
728 * This call attempts to connect to using the connection properties
729 * specified. When this method returns the connection has been
730 * established, the server's identity been verified and the connection is
731 * ready for user authentication. To use this method first create a
732 * properties instance and set the required fields.
733 * </p>
734 * <blockquote><pre>
735 * SshConnectionProperties properties = new
736 * SshConnectionProperties();
737 * properties.setHostname("hostname");
738 * properties.setPort(22); // Defaults to 22
739 * // Set the prefered client->server encryption
740 * ssh.setPrefCSEncryption("blowfish-cbc");
741 * // Set the prefered server->client encrpytion
742 * ssh.setPrefSCEncrpyion("3des-cbc");
743 * ssh.connect(properties);
744 * </pre></blockquote>
745 *
746 * <p>
747 * Host key verification will be performed using the host key verification
748 * instance provided:<br>
749 * <blockquote><pre>
750 * // Connect and consult $HOME/.ssh/known_hosts
751 * ssh.connect("hostname", new ConsoleKnownHostsKeyVerification());
752 * // Connect and allow any host
753 * ssh.connect("hostname", new
754 * IgnoreHostKeyVerification());
755 * </pre></blockquote>
756 * You can provide your own host key verification process by implementing the
757 * <code>HostKeyVerification</code> interface.
758 * </p>
759 *
760 * @param properties The connection properties
761 * @param hostVerification The host key verification instance to consult
762 * for host key validation
763 *
764 * @exception UnknownHostException If the host is unknown
765 * @exception IOException If an IO error occurs during the connect
766 * operation
767 *
768 * @since 0.2.0
769 */
770 public void connect(SshConnectionProperties properties,
771 HostKeyVerification hostVerification)
772 throws UnknownHostException, IOException {
773 TransportProvider provider = TransportProviderFactory.connectTransportProvider(properties /*, connectTimeout*/,
774 socketTimeout);
775
776 // Start the transport protocol
777 transport = new TransportProtocolClient(hostVerification);
778 transport.addEventHandler(eventHandler);
779 transport.startTransportProtocol(provider, properties);
780
781 // Start the authentication protocol
782 authentication = new AuthenticationProtocolClient();
783 authentication.addEventListener(eventHandler);
784 transport.requestService(authentication);
785 connection = new ConnectionProtocol();
786
787 if (useDefaultForwarding) {
788 forwarding = new ForwardingClient(connection);
789 }
790 }
791
792 /**
793 * <p>
794 * Sets the timeout value for the key exchange.
795 * </p>
796 *
797 * <p>
798 * When this time limit is reached the transport protocol will initiate a
799 * key re-exchange. The default value is one hour with the minumin timeout
800 * being 60 seconds.
801 * </p>
802 *
803 * @param seconds The number of seconds beofre key re-exchange
804 *
805 * @exception IOException If the timeout value is invalid
806 *
807 * @since 0.2.0
808 */
809 public void setKexTimeout(long seconds) throws IOException {
810 transport.setKexTimeout(seconds);
811 }
812
813 /**
814 * <p>
815 * Sets the key exchance transfer limit in kilobytes.
816 * </p>
817 *
818 * <p>
819 * Once this amount of data has been transfered the transport protocol will
820 * initiate a key re-exchange. The default value is one gigabyte of data
821 * with the mimimun value of 10 kilobytes.
822 * </p>
823 *
824 * @param kilobytes The data transfer limit in kilobytes
825 *
826 * @exception IOException If the data transfer limit is invalid
827 */
828 public void setKexTransferLimit(long kilobytes) throws IOException {
829 transport.setKexTransferLimit(kilobytes);
830 }
831
832 /**
833 * <p>
834 * Set's the send ignore flag to send random data packets.
835 * </p>
836 *
837 * <p>
838 * If this flag is set to true, then the transport protocol will send
839 * additional SSH_MSG_IGNORE packets with random data.
840 * </p>
841 *
842 * @param sendIgnore true if you want to turn on random packet data,
843 * otherwise false
844 *
845 * @since 0.2.0
846 */
847 public void setSendIgnore(boolean sendIgnore) {
848 transport.setSendIgnore(sendIgnore);
849 }
850
851 /**
852 * <p>
853 * Turn the default forwarding manager on/off.
854 * </p>
855 *
856 * <p>
857 * If this flag is set to false before connection, the client will not
858 * create a port forwarding manager. Use this to provide you own
859 * forwarding implementation.
860 * </p>
861 *
862 * @param useDefaultForwarding Set to false if you not wish to use the
863 * default forwarding manager.
864 *
865 * @since 0.2.0
866 */
867 public void setUseDefaultForwarding(boolean useDefaultForwarding) {
868 this.useDefaultForwarding = useDefaultForwarding;
869 }
870
871 /**
872 * <p>
873 * Disconnect the client.
874 * </p>
875 *
876 * @since 0.2.0
877 */
878 public void disconnect() {
879 if (connection != null) {
880 connection.stop();
881 }
882
883 if (transport != null) {
884 transport.disconnect("Terminating connection");
885 }
886 }
887
888 /**
889 * <p>
890 * Returns the number of bytes transmitted to the remote server.
891 * </p>
892 *
893 * @return The number of bytes transmitted
894 *
895 * @since 0.2.0
896 */
897 public long getOutgoingByteCount() {
898 return transport.getOutgoingByteCount();
899 }
900
901 /**
902 * <p>
903 * Returns the number of bytes received from the remote server.
904 * </p>
905 *
906 * @return The number of bytes received
907 *
908 * @since 0.2.0
909 */
910 public long getIncomingByteCount() {
911 return transport.getIncomingByteCount();
912 }
913
914 /**
915 * <p>
916 * Returns the number of active channels for this client.
917 * </p>
918 *
919 * <p>
920 * This is the total count of sessions, port forwarding, sftp, scp and
921 * custom channels currently open.
922 * </p>
923 *
924 * @return The number of active channels
925 *
926 * @since 0.2.0
927 */
928 public int getActiveChannelCount() {
929 synchronized (activeChannels) {
930 return activeChannels.size();
931 }
932 }
933
934 /**
935 * <p>
936 * Returns the list of active channels.
937 * </p>
938 *
939 * @return The list of active channels
940 *
941 * @since 0.2.0
942 */
943 public List getActiveChannels() {
944 synchronized (activeChannels) {
945 return (List) activeChannels.clone();
946 }
947 }
948
949 /**
950 * <p>
951 * Returns true if there is an active session channel of the specified
952 * type.
953 * </p>
954 *
955 * <p>
956 * When a session is created, it is assigned a default type. For instance,
957 * when a session is created it as a type of "uninitialized"; however when
958 * a shell is started on the session, the type is set to "shell". This
959 * also occurs for commands where the type is set to the command which is
960 * executed and subsystems where the type is set to the subsystem name.
961 * This allows each session to be saved in the active session channel's
962 * list and recalled later. It is also possible to set the session
963 * channel's type using the setSessionType method of the
964 * <code>SessionChannelClient</code> class.
965 * </p>
966 * <blockquote><pre>
967 * if(ssh.hasActiveSession("shell")) {
968 * SessionChannelClient session =
969 * ssh.getActiveSession("shell");
970 * }
971 * </pre></blockquote>
972 *
973 * @param type The string specifying the channel type
974 *
975 * @return true if an active session channel exists, otherwise false
976 *
977 * @since 0.2.0
978 */
979 public boolean hasActiveSession(String type) {
980 Iterator it = activeChannels.iterator();
981 Object obj;
982
983 while (it.hasNext()) {
984 obj = it.next();
985
986 if (obj instanceof SessionChannelClient) {
987 if (((SessionChannelClient) obj).getSessionType().equals(type)) {
988 return true;
989 }
990 }
991 }
992
993 return false;
994 }
995
996 /**
997 * <p>
998 * Returns the active session channel of the given type.
999 * </p>
1000 *
1001 * @param type The type fo session channel
1002 *
1003 * @return The session channel instance
1004 *
1005 * @exception IOException If the session type does not exist
1006 *
1007 * @since 0.2.0
1008 */
1009 public SessionChannelClient getActiveSession(String type)
1010 throws IOException {
1011 Iterator it = activeChannels.iterator();
1012 Object obj;
1013
1014 while (it.hasNext()) {
1015 obj = it.next();
1016
1017 if (obj instanceof SessionChannelClient) {
1018 if (((SessionChannelClient) obj).getSessionType().equals(type)) {
1019 return (SessionChannelClient) obj;
1020 }
1021 }
1022 }
1023
1024 throw new IOException("There are no active " + type + " sessions");
1025 }
1026
1027 /**
1028 * Determine whether the channel supplied is an active channel
1029 *
1030 * @param channel
1031 *
1032 * @return
1033 */
1034 public boolean isActiveChannel(Channel channel) {
1035 return activeChannels.contains(channel);
1036 }
1037
1038 /**
1039 * <p>
1040 * Open's a session channel on the remote server.
1041 * </p>
1042 *
1043 * <p>
1044 * A session channel may be used to start the user's shell, execute a
1045 * command or start a subsystem such as SFTP.
1046 * </p>
1047 *
1048 * @return An new session channel
1049 *
1050 * @exception IOException If authentication has not been completed, the
1051 * server refuses to open the channel or a general IO error
1052 * occurs
1053 *
1054 * @see com.sshtools.j2ssh.session.SessionChannelClient
1055 * @since 0.2.0
1056 */
1057 public SessionChannelClient openSessionChannel() throws IOException {
1058 return openSessionChannel(null);
1059 }
1060
1061 /**
1062 *
1063 * <p>
1064 * Open's a session channel on the remote server.
1065 * </p>
1066 *
1067 * <p>
1068 * A session channel may be used to start the user's shell, execute a
1069 * command or start a subsystem such as SFTP.
1070 * </p>
1071 *
1072 * @param eventListener an event listner interface to add to the channel
1073 *
1074 * @return
1075 *
1076 * @throws IOException
1077 * @throws SshException
1078 */
1079 public SessionChannelClient openSessionChannel(
1080 ChannelEventListener eventListener) throws IOException {
1081 if (authenticationState != AuthenticationProtocolState.COMPLETE) {
1082 throw new SshException("Authentication has not been completed!");
1083 }
1084
1085 SessionChannelClient session = new SessionChannelClient();
1086 session.addEventListener(activeChannelListener);
1087
1088 if (!connection.openChannel(session, eventListener)) {
1089 throw new SshException("The server refused to open a session");
1090 }
1091
1092 return session;
1093 }
1094
1095 /**
1096 * <p>
1097 * Open an SFTP client for file transfer operations.
1098 * </p>
1099 * <blockquote><pre>
1100 * SftpClient sftp = ssh.openSftpClient();
1101 * sftp.cd("foo");
1102 * sftp.put("somefile.txt");
1103 * sftp.quit();
1104 * </pre></blockquote>
1105 *
1106 * @return Returns an initialized SFTP client
1107 *
1108 * @exception IOException If an IO error occurs during the operation
1109 *
1110 * @see SftpClient
1111 * @since 0.2.0
1112 */
1113 public SftpClient openSftpClient() throws IOException {
1114 return openSftpClient(null);
1115 }
1116
1117 /**
1118 * <p>
1119 * Open an SFTP client for file transfer operations. Adds the supplied
1120 * event listener to the underlying channel.
1121 * </p>
1122 *
1123 * @param eventListener
1124 *
1125 * @return
1126 *
1127 * @throws IOException
1128 */
1129 public SftpClient openSftpClient(ChannelEventListener eventListener)
1130 throws IOException {
1131 SftpClient sftp = new SftpClient(this, eventListener);
1132 activeSftpClients.add(sftp);
1133
1134 return sftp;
1135 }
1136
1137 /**
1138 * Determine if there are existing sftp clients open
1139 *
1140 * @return
1141 */
1142 public boolean hasActiveSftpClient() {
1143 synchronized (activeSftpClients) {
1144 return activeSftpClients.size() > 0;
1145 }
1146 }
1147
1148 /**
1149 * Get an active sftp client
1150 *
1151 * @return
1152 *
1153 * @throws IOException
1154 * @throws SshException
1155 */
1156 public SftpClient getActiveSftpClient() throws IOException {
1157 synchronized (activeSftpClients) {
1158 if (activeSftpClients.size() > 0) {
1159 return (SftpClient) activeSftpClients.get(0);
1160 } else {
1161 throw new SshException("There are no active SFTP clients");
1162 }
1163 }
1164 }
1165
1166 /**
1167 * <p>
1168 * Open an SCP client for file transfer operations where SFTP is not
1169 * supported.
1170 * </p>
1171 *
1172 * <p>
1173 * Sets the local working directory to the user's home directory
1174 * </p>
1175 * <blockquote><pre>
1176 * ScpClient scp = ssh.openScpClient();
1177 * scp.put("somefile.txt");
1178 * </pre></blockquote>
1179 *
1180 * @return An initialized SCP client
1181 *
1182 * @exception IOException If an IO error occurs during the operation
1183 *
1184 * @see ScpClient
1185 * @since 0.2.0
1186 */
1187 public ScpClient openScpClient() throws IOException {
1188 return new ScpClient(new File(System.getProperty("user.home")), this,
1189 false, activeChannelListener);
1190 }
1191
1192 /**
1193 * <p>
1194 * Open an SCP client for file transfer operations where SFTP is not
1195 * supported.
1196 * </p>
1197 *
1198 * <p>
1199 * This method sets a local current working directory.
1200 * </p>
1201 * <blockquote><pre>
1202 * ScpClient scp = ssh.openScpClient("foo");
1203 * scp.put("somefile.txt");
1204 * </pre></blockquote>
1205 *
1206 * @param cwd The local directory as the base for all local files
1207 *
1208 * @return An intialized SCP client
1209 *
1210 * @exception IOException If an IO error occurs during the operation
1211 *
1212 * @see SftpClient
1213 * @since 0.2.0
1214 */
1215 public ScpClient openScpClient(File cwd) throws IOException {
1216 return new ScpClient(cwd, this, false, activeChannelListener);
1217 }
1218
1219 /**
1220 * <p>
1221 * Open's an Sftp channel.
1222 * </p>
1223 *
1224 * <p>
1225 * Use this sftp channel if you require a lower level api into the SFTP
1226 * protocol.
1227 * </p>
1228 *
1229 * @return an initialized sftp subsystem instance
1230 *
1231 * @throws IOException if an IO error occurs or the channel cannot be
1232 * opened
1233 *
1234 * @since 0.2.0
1235 */
1236 public SftpSubsystemClient openSftpChannel() throws IOException {
1237 return openSftpChannel(null);
1238 }
1239
1240 /**
1241 * Open an SftpSubsystemChannel. For advanced use only
1242 *
1243 * @param eventListener
1244 *
1245 * @return
1246 *
1247 * @throws IOException
1248 * @throws SshException
1249 */
1250 public SftpSubsystemClient openSftpChannel(
1251 ChannelEventListener eventListener) throws IOException {
1252 SessionChannelClient session = openSessionChannel(eventListener);
1253 SftpSubsystemClient sftp = new SftpSubsystemClient();
1254
1255 if (!openChannel(sftp)) {
1256 throw new SshException("The SFTP subsystem failed to start");
1257 }
1258
1259 // Initialize SFTP
1260 if (!sftp.initialize()) {
1261 throw new SshException(
1262 "The SFTP Subsystem could not be initialized");
1263 }
1264
1265 return sftp;
1266 }
1267
1268 /**
1269 * <p>
1270 * Open's a channel.
1271 * </p>
1272 *
1273 * <p>
1274 * Call this method to open a custom channel. This method is used by all
1275 * other channel opening methods. For example the openSessionChannel
1276 * method could be implemented as:<br>
1277 * <blockquote><pre>
1278 * SessionChannelClient session =
1279 * new SessionChannelClient();
1280 * if(ssh.openChannel(session)) {
1281 * // Channel is now open
1282 * }
1283 * </pre></blockquote>
1284 * </p>
1285 *
1286 * @param channel
1287 *
1288 * @return true if the channel was opened, otherwise false
1289 *
1290 * @exception IOException if an IO error occurs
1291 * @throws SshException
1292 *
1293 * @since 0.2.0
1294 */
1295 public boolean openChannel(Channel channel) throws IOException {
1296 if (authenticationState != AuthenticationProtocolState.COMPLETE) {
1297 throw new SshException("Authentication has not been completed!");
1298 }
1299
1300 // Open the channel providing our channel listener so we can track
1301 return connection.openChannel(channel, activeChannelListener);
1302 }
1303
1304 /**
1305 * <p>
1306 * Instructs the underlying connection protocol to allow channels of the
1307 * given type to be opened by the server.
1308 * </p>
1309 *
1310 * <p>
1311 * The client does not allow channels to be opened by default. Call this
1312 * method to allow the server to open channels by providing a
1313 * <code>ChannelFactory</code> implementation to create instances upon
1314 * request.
1315 * </p>
1316 *
1317 * @param channelName The channel type name
1318 * @param cf The factory implementation that will create instances of the
1319 * channel when a channel open request is recieved.
1320 *
1321 * @exception IOException if an IO error occurs
1322 *
1323 * @since 0.2.0
1324 */
1325 public void allowChannelOpen(String channelName, ChannelFactory cf)
1326 throws IOException {
1327 connection.addChannelFactory(channelName, cf);
1328 }
1329
1330 /**
1331 * <p>
1332 * Stops the specified channel type from being opended.
1333 * </p>
1334 *
1335 * @param channelName The channel type name
1336 *
1337 * @throws IOException if an IO error occurs
1338 *
1339 * @since 0.2.1
1340 */
1341 public void denyChannelOpen(String channelName) throws IOException {
1342 connection.removeChannelFactory(channelName);
1343 }
1344
1345 /**
1346 * <p>
1347 * Send a global request to the server.
1348 * </p>
1349 *
1350 * <p>
1351 * The SSH specification provides a global request mechanism which is used
1352 * for starting/stopping remote forwarding. This is a general mechanism
1353 * which can be used for other purposes if the server supports the global
1354 * requests.
1355 * </p>
1356 *
1357 * @param requestName The name of the global request
1358 * @param wantReply true if the server should send an explict reply
1359 * @param requestData the global request data
1360 *
1361 * @return true if the global request succeeded or wantReply==false,
1362 * otherwise false
1363 *
1364 * @throws IOException if an IO error occurs
1365 *
1366 * @since 0.2.0
1367 */
1368 public byte[] sendGlobalRequest(String requestName, boolean wantReply,
1369 byte[] requestData) throws IOException {
1370 return connection.sendGlobalRequest(requestName, wantReply, requestData);
1371 }
1372
1373 /**
1374 * <p>
1375 * Implements the <code>ChannelEventListener</code> interface to provide
1376 * real time tracking of active channels.
1377 * </p>
1378 */
1379 class ActiveChannelEventListener extends ChannelEventAdapter {
1380 /**
1381 * <p>
1382 * Adds the channel to the active channel list.
1383 * </p>
1384 *
1385 * @param channel The channel being opened
1386 */
1387 public void onChannelOpen(Channel channel) {
1388 synchronized (activeChannels) {
1389 activeChannels.add(channel);
1390 }
1391 }
1392
1393 /**
1394 * <p>
1395 * Removes the closed channel from the clients active channels list.
1396 * </p>
1397 *
1398 * @param channel The channle being closed
1399 */
1400 public void onChannelClose(Channel channel) {
1401 synchronized (activeChannels) {
1402 activeChannels.remove(channel);
1403 }
1404 }
1405 }
1406 }