comparison src/com/trilead/ssh2/Connection.java @ 0:0ce5cc452d02

initial version
author Carl Byington <carl@five-ten-sg.com>
date Thu, 22 May 2014 10:41:19 -0700
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:0ce5cc452d02
1
2 package com.trilead.ssh2;
3
4 import java.io.CharArrayWriter;
5 import java.io.File;
6 import java.io.FileReader;
7 import java.io.IOException;
8 import java.net.InetSocketAddress;
9 import java.net.SocketTimeoutException;
10 import java.security.KeyPair;
11 import java.security.SecureRandom;
12 import java.security.Security;
13 import java.util.Set;
14 import java.util.Vector;
15
16 import com.trilead.ssh2.auth.AuthenticationManager;
17 import com.trilead.ssh2.channel.ChannelManager;
18 import com.trilead.ssh2.crypto.CryptoWishList;
19 import com.trilead.ssh2.crypto.cipher.BlockCipherFactory;
20 import com.trilead.ssh2.crypto.digest.MAC;
21 import com.trilead.ssh2.log.Logger;
22 import com.trilead.ssh2.packets.PacketIgnore;
23 import com.trilead.ssh2.transport.KexManager;
24 import com.trilead.ssh2.transport.TransportManager;
25 import com.trilead.ssh2.util.TimeoutService;
26 import com.trilead.ssh2.util.TimeoutService.TimeoutToken;
27
28 /**
29 * A <code>Connection</code> is used to establish an encrypted TCP/IP
30 * connection to a SSH-2 server.
31 * <p>
32 * Typically, one
33 * <ol>
34 * <li>creates a {@link #Connection(String) Connection} object.</li>
35 * <li>calls the {@link #connect() connect()} method.</li>
36 * <li>calls some of the authentication methods (e.g.,
37 * {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()}).</li>
38 * <li>calls one or several times the {@link #openSession() openSession()}
39 * method.</li>
40 * <li>finally, one must close the connection and release resources with the
41 * {@link #close() close()} method.</li>
42 * </ol>
43 *
44 * @author Christian Plattner, plattner@trilead.com
45 * @version $Id: Connection.java,v 1.3 2008/04/01 12:38:09 cplattne Exp $
46 */
47
48 public class Connection {
49 /**
50 * The identifier presented to the SSH-2 server.
51 */
52 public final static String identification = "TrileadSSH2Java_213";
53
54 /**
55 * Will be used to generate all random data needed for the current
56 * connection. Note: SecureRandom.nextBytes() is thread safe.
57 */
58 private SecureRandom generator;
59
60 /**
61 * Unless you know what you are doing, you will never need this.
62 *
63 * @return The list of supported cipher algorithms by this implementation.
64 */
65
66 public static synchronized String[] getAvailableCiphers() {
67 return BlockCipherFactory.getDefaultCipherList();
68 }
69
70 /**
71 * Unless you know what you are doing, you will never need this.
72 *
73 * @return The list of supported MAC algorthims by this implementation.
74 */
75
76 public static synchronized String[] getAvailableMACs() {
77 return MAC.getMacList();
78 }
79
80 /**
81 * Unless you know what you are doing, you will never need this.
82 *
83 * @return The list of supported server host key algorthims by this
84 * implementation.
85 */
86
87 public static synchronized String[] getAvailableServerHostKeyAlgorithms() {
88 return KexManager.getDefaultServerHostkeyAlgorithmList();
89 }
90
91 private AuthenticationManager am;
92
93 private boolean authenticated = false;
94 private boolean compression = false;
95 private ChannelManager cm;
96
97 private CryptoWishList cryptoWishList = new CryptoWishList();
98
99 private DHGexParameters dhgexpara = new DHGexParameters();
100
101 private final String hostname;
102
103 private final int port;
104
105 private TransportManager tm;
106
107 private boolean tcpNoDelay = false;
108
109 private ProxyData proxyData = null;
110
111 private Vector<ConnectionMonitor> connectionMonitors = new Vector<ConnectionMonitor>();
112
113 /**
114 * Prepares a fresh <code>Connection</code> object which can then be used
115 * to establish a connection to the specified SSH-2 server.
116 * <p>
117 * Same as {@link #Connection(String, int) Connection(hostname, 22)}.
118 *
119 * @param hostname
120 * the hostname of the SSH-2 server.
121 */
122 public Connection(String hostname) {
123 this(hostname, 22);
124 }
125
126 /**
127 * Prepares a fresh <code>Connection</code> object which can then be used
128 * to establish a connection to the specified SSH-2 server.
129 *
130 * @param hostname
131 * the host where we later want to connect to.
132 * @param port
133 * port on the server, normally 22.
134 */
135 public Connection(String hostname, int port) {
136 this.hostname = hostname;
137 this.port = port;
138 }
139
140 /**
141 * After a successful connect, one has to authenticate oneself. This method
142 * is based on DSA (it uses DSA to sign a challenge sent by the server).
143 * <p>
144 * If the authentication phase is complete, <code>true</code> will be
145 * returned. If the server does not accept the request (or if further
146 * authentication steps are needed), <code>false</code> is returned and
147 * one can retry either by using this or any other authentication method
148 * (use the <code>getRemainingAuthMethods</code> method to get a list of
149 * the remaining possible methods).
150 *
151 * @param user
152 * A <code>String</code> holding the username.
153 * @param pem
154 * A <code>String</code> containing the DSA private key of the
155 * user in OpenSSH key format (PEM, you can't miss the
156 * "-----BEGIN DSA PRIVATE KEY-----" tag). The string may contain
157 * linefeeds.
158 * @param password
159 * If the PEM string is 3DES encrypted ("DES-EDE3-CBC"), then you
160 * must specify the password. Otherwise, this argument will be
161 * ignored and can be set to <code>null</code>.
162 *
163 * @return whether the connection is now authenticated.
164 * @throws IOException
165 *
166 * @deprecated You should use one of the
167 * {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()}
168 * methods, this method is just a wrapper for it and will
169 * disappear in future builds.
170 *
171 */
172
173 public synchronized boolean authenticateWithDSA(String user, String pem, String password) throws IOException {
174 if (tm == null)
175 throw new IllegalStateException("Connection is not established!");
176
177 if (authenticated)
178 throw new IllegalStateException("Connection is already authenticated!");
179
180 if (am == null)
181 am = new AuthenticationManager(tm);
182
183 if (cm == null)
184 cm = new ChannelManager(tm);
185
186 if (user == null)
187 throw new IllegalArgumentException("user argument is null");
188
189 if (pem == null)
190 throw new IllegalArgumentException("pem argument is null");
191
192 authenticated = am.authenticatePublicKey(user, pem.toCharArray(), password, getOrCreateSecureRND());
193 return authenticated;
194 }
195
196 /**
197 * A wrapper that calls
198 * {@link #authenticateWithKeyboardInteractive(String, String[], InteractiveCallback)
199 * authenticateWithKeyboardInteractivewith} a <code>null</code> submethod
200 * list.
201 *
202 * @param user
203 * A <code>String</code> holding the username.
204 * @param cb
205 * An <code>InteractiveCallback</code> which will be used to
206 * determine the responses to the questions asked by the server.
207 * @return whether the connection is now authenticated.
208 * @throws IOException
209 */
210
211 public synchronized boolean authenticateWithKeyboardInteractive(String user, InteractiveCallback cb)
212 throws IOException {
213 return authenticateWithKeyboardInteractive(user, null, cb);
214 }
215
216 /**
217 * After a successful connect, one has to authenticate oneself. This method
218 * is based on "keyboard-interactive", specified in
219 * draft-ietf-secsh-auth-kbdinteract-XX. Basically, you have to define a
220 * callback object which will be feeded with challenges generated by the
221 * server. Answers are then sent back to the server. It is possible that the
222 * callback will be called several times during the invocation of this
223 * method (e.g., if the server replies to the callback's answer(s) with
224 * another challenge...)
225 * <p>
226 * If the authentication phase is complete, <code>true</code> will be
227 * returned. If the server does not accept the request (or if further
228 * authentication steps are needed), <code>false</code> is returned and
229 * one can retry either by using this or any other authentication method
230 * (use the <code>getRemainingAuthMethods</code> method to get a list of
231 * the remaining possible methods).
232 * <p>
233 * Note: some SSH servers advertise "keyboard-interactive", however, any
234 * interactive request will be denied (without having sent any challenge to
235 * the client).
236 *
237 * @param user
238 * A <code>String</code> holding the username.
239 * @param submethods
240 * An array of submethod names, see
241 * draft-ietf-secsh-auth-kbdinteract-XX. May be <code>null</code>
242 * to indicate an empty list.
243 * @param cb
244 * An <code>InteractiveCallback</code> which will be used to
245 * determine the responses to the questions asked by the server.
246 *
247 * @return whether the connection is now authenticated.
248 * @throws IOException
249 */
250
251 public synchronized boolean authenticateWithKeyboardInteractive(String user, String[] submethods,
252 InteractiveCallback cb) throws IOException {
253 if (cb == null)
254 throw new IllegalArgumentException("Callback may not ne NULL!");
255
256 if (tm == null)
257 throw new IllegalStateException("Connection is not established!");
258
259 if (authenticated)
260 throw new IllegalStateException("Connection is already authenticated!");
261
262 if (am == null)
263 am = new AuthenticationManager(tm);
264
265 if (cm == null)
266 cm = new ChannelManager(tm);
267
268 if (user == null)
269 throw new IllegalArgumentException("user argument is null");
270
271 authenticated = am.authenticateInteractive(user, submethods, cb);
272 return authenticated;
273 }
274
275 /**
276 * After a successful connect, one has to authenticate oneself. This method
277 * sends username and password to the server.
278 * <p>
279 * If the authentication phase is complete, <code>true</code> will be
280 * returned. If the server does not accept the request (or if further
281 * authentication steps are needed), <code>false</code> is returned and
282 * one can retry either by using this or any other authentication method
283 * (use the <code>getRemainingAuthMethods</code> method to get a list of
284 * the remaining possible methods).
285 * <p>
286 * Note: if this method fails, then please double-check that it is actually
287 * offered by the server (use
288 * {@link #getRemainingAuthMethods(String) getRemainingAuthMethods()}.
289 * <p>
290 * Often, password authentication is disabled, but users are not aware of
291 * it. Many servers only offer "publickey" and "keyboard-interactive".
292 * However, even though "keyboard-interactive" *feels* like password
293 * authentication (e.g., when using the putty or openssh clients) it is
294 * *not* the same mechanism.
295 *
296 * @param user
297 * @param password
298 * @return if the connection is now authenticated.
299 * @throws IOException
300 */
301
302 public synchronized boolean authenticateWithPassword(String user, String password) throws IOException {
303 if (tm == null)
304 throw new IllegalStateException("Connection is not established!");
305
306 if (authenticated)
307 throw new IllegalStateException("Connection is already authenticated!");
308
309 if (am == null)
310 am = new AuthenticationManager(tm);
311
312 if (cm == null)
313 cm = new ChannelManager(tm);
314
315 if (user == null)
316 throw new IllegalArgumentException("user argument is null");
317
318 if (password == null)
319 throw new IllegalArgumentException("password argument is null");
320
321 authenticated = am.authenticatePassword(user, password);
322 return authenticated;
323 }
324
325 /**
326 * After a successful connect, one has to authenticate oneself. This method
327 * can be used to explicitly use the special "none" authentication method
328 * (where only a username has to be specified).
329 * <p>
330 * Note 1: The "none" method may always be tried by clients, however as by
331 * the specs, the server will not explicitly announce it. In other words,
332 * the "none" token will never show up in the list returned by
333 * {@link #getRemainingAuthMethods(String)}.
334 * <p>
335 * Note 2: no matter which one of the authenticateWithXXX() methods you
336 * call, the library will always issue exactly one initial "none"
337 * authentication request to retrieve the initially allowed list of
338 * authentication methods by the server. Please read RFC 4252 for the
339 * details.
340 * <p>
341 * If the authentication phase is complete, <code>true</code> will be
342 * returned. If further authentication steps are needed, <code>false</code>
343 * is returned and one can retry by any other authentication method (use the
344 * <code>getRemainingAuthMethods</code> method to get a list of the
345 * remaining possible methods).
346 *
347 * @param user
348 * @return if the connection is now authenticated.
349 * @throws IOException
350 */
351
352 public synchronized boolean authenticateWithNone(String user) throws IOException {
353 if (tm == null)
354 throw new IllegalStateException("Connection is not established!");
355
356 if (authenticated)
357 throw new IllegalStateException("Connection is already authenticated!");
358
359 if (am == null)
360 am = new AuthenticationManager(tm);
361
362 if (cm == null)
363 cm = new ChannelManager(tm);
364
365 if (user == null)
366 throw new IllegalArgumentException("user argument is null");
367
368 /* Trigger the sending of the PacketUserauthRequestNone packet */
369 /* (if not already done) */
370 authenticated = am.authenticateNone(user);
371 return authenticated;
372 }
373
374 /**
375 * After a successful connect, one has to authenticate oneself. The
376 * authentication method "publickey" works by signing a challenge sent by
377 * the server. The signature is either DSA or RSA based - it just depends on
378 * the type of private key you specify, either a DSA or RSA private key in
379 * PEM format. And yes, this is may seem to be a little confusing, the
380 * method is called "publickey" in the SSH-2 protocol specification, however
381 * since we need to generate a signature, you actually have to supply a
382 * private key =).
383 * <p>
384 * The private key contained in the PEM file may also be encrypted
385 * ("Proc-Type: 4,ENCRYPTED"). The library supports DES-CBC and DES-EDE3-CBC
386 * encryption, as well as the more exotic PEM encrpytions AES-128-CBC,
387 * AES-192-CBC and AES-256-CBC.
388 * <p>
389 * If the authentication phase is complete, <code>true</code> will be
390 * returned. If the server does not accept the request (or if further
391 * authentication steps are needed), <code>false</code> is returned and
392 * one can retry either by using this or any other authentication method
393 * (use the <code>getRemainingAuthMethods</code> method to get a list of
394 * the remaining possible methods).
395 * <p>
396 * NOTE PUTTY USERS: Event though your key file may start with
397 * "-----BEGIN..." it is not in the expected format. You have to convert it
398 * to the OpenSSH key format by using the "puttygen" tool (can be downloaded
399 * from the Putty website). Simply load your key and then use the
400 * "Conversions/Export OpenSSH key" functionality to get a proper PEM file.
401 *
402 * @param user
403 * A <code>String</code> holding the username.
404 * @param pemPrivateKey
405 * A <code>char[]</code> containing a DSA or RSA private key of
406 * the user in OpenSSH key format (PEM, you can't miss the
407 * "-----BEGIN DSA PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE
408 * KEY-----" tag). The char array may contain
409 * linebreaks/linefeeds.
410 * @param password
411 * If the PEM structure is encrypted ("Proc-Type: 4,ENCRYPTED")
412 * then you must specify a password. Otherwise, this argument
413 * will be ignored and can be set to <code>null</code>.
414 *
415 * @return whether the connection is now authenticated.
416 * @throws IOException
417 */
418
419 public synchronized boolean authenticateWithPublicKey(String user, char[] pemPrivateKey, String password)
420 throws IOException {
421 if (tm == null)
422 throw new IllegalStateException("Connection is not established!");
423
424 if (authenticated)
425 throw new IllegalStateException("Connection is already authenticated!");
426
427 if (am == null)
428 am = new AuthenticationManager(tm);
429
430 if (cm == null)
431 cm = new ChannelManager(tm);
432
433 if (user == null)
434 throw new IllegalArgumentException("user argument is null");
435
436 if (pemPrivateKey == null)
437 throw new IllegalArgumentException("pemPrivateKey argument is null");
438
439 authenticated = am.authenticatePublicKey(user, pemPrivateKey, password, getOrCreateSecureRND());
440 return authenticated;
441 }
442
443 /**
444 * After a successful connect, one has to authenticate oneself. The
445 * authentication method "publickey" works by signing a challenge sent by
446 * the server. The signature is either DSA or RSA based - it just depends on
447 * the type of private key you specify, either a DSA or RSA private key in
448 * PEM format. And yes, this is may seem to be a little confusing, the
449 * method is called "publickey" in the SSH-2 protocol specification, however
450 * since we need to generate a signature, you actually have to supply a
451 * private key =).
452 * <p>
453 * If the authentication phase is complete, <code>true</code> will be
454 * returned. If the server does not accept the request (or if further
455 * authentication steps are needed), <code>false</code> is returned and
456 * one can retry either by using this or any other authentication method
457 * (use the <code>getRemainingAuthMethods</code> method to get a list of
458 * the remaining possible methods).
459 *
460 * @param user
461 * A <code>String</code> holding the username.
462 * @param key
463 * A <code>RSAPrivateKey</code> or <code>DSAPrivateKey</code>
464 * containing a DSA or RSA private key of
465 * the user in Trilead object format.
466 *
467 * @return whether the connection is now authenticated.
468 * @throws IOException
469 */
470
471 public synchronized boolean authenticateWithPublicKey(String user, KeyPair pair)
472 throws IOException {
473 if (tm == null)
474 throw new IllegalStateException("Connection is not established!");
475
476 if (authenticated)
477 throw new IllegalStateException("Connection is already authenticated!");
478
479 if (am == null)
480 am = new AuthenticationManager(tm);
481
482 if (cm == null)
483 cm = new ChannelManager(tm);
484
485 if (user == null)
486 throw new IllegalArgumentException("user argument is null");
487
488 if (pair == null)
489 throw new IllegalArgumentException("Key pair argument is null");
490
491 authenticated = am.authenticatePublicKey(user, pair, getOrCreateSecureRND());
492 return authenticated;
493 }
494
495 /**
496 * A convenience wrapper function which reads in a private key (PEM format,
497 * either DSA or RSA) and then calls
498 * <code>authenticateWithPublicKey(String, char[], String)</code>.
499 * <p>
500 * NOTE PUTTY USERS: Event though your key file may start with
501 * "-----BEGIN..." it is not in the expected format. You have to convert it
502 * to the OpenSSH key format by using the "puttygen" tool (can be downloaded
503 * from the Putty website). Simply load your key and then use the
504 * "Conversions/Export OpenSSH key" functionality to get a proper PEM file.
505 *
506 * @param user
507 * A <code>String</code> holding the username.
508 * @param pemFile
509 * A <code>File</code> object pointing to a file containing a
510 * DSA or RSA private key of the user in OpenSSH key format (PEM,
511 * you can't miss the "-----BEGIN DSA PRIVATE KEY-----" or
512 * "-----BEGIN RSA PRIVATE KEY-----" tag).
513 * @param password
514 * If the PEM file is encrypted then you must specify the
515 * password. Otherwise, this argument will be ignored and can be
516 * set to <code>null</code>.
517 *
518 * @return whether the connection is now authenticated.
519 * @throws IOException
520 */
521
522 public synchronized boolean authenticateWithPublicKey(String user, File pemFile, String password)
523 throws IOException {
524 if (pemFile == null)
525 throw new IllegalArgumentException("pemFile argument is null");
526
527 char[] buff = new char[256];
528 CharArrayWriter cw = new CharArrayWriter();
529 FileReader fr = new FileReader(pemFile);
530
531 while (true) {
532 int len = fr.read(buff);
533
534 if (len < 0)
535 break;
536
537 cw.write(buff, 0, len);
538 }
539
540 fr.close();
541 return authenticateWithPublicKey(user, cw.toCharArray(), password);
542 }
543
544 /**
545 * Add a {@link ConnectionMonitor} to this connection. Can be invoked at any
546 * time, but it is best to add connection monitors before invoking
547 * <code>connect()</code> to avoid glitches (e.g., you add a connection
548 * monitor after a successful connect(), but the connection has died in the
549 * mean time. Then, your connection monitor won't be notified.)
550 * <p>
551 * You can add as many monitors as you like.
552 *
553 * @see ConnectionMonitor
554 *
555 * @param cmon
556 * An object implementing the <code>ConnectionMonitor</code>
557 * interface.
558 */
559
560 public synchronized void addConnectionMonitor(ConnectionMonitor cmon) {
561 if (cmon == null)
562 throw new IllegalArgumentException("cmon argument is null");
563
564 connectionMonitors.addElement(cmon);
565
566 if (tm != null)
567 tm.setConnectionMonitors(connectionMonitors);
568 }
569
570 /**
571 * Controls whether compression is used on the link or not.
572 * <p>
573 * Note: This can only be called before connect()
574 * @param enabled whether to enable compression
575 * @throws IOException
576 */
577
578 public synchronized void setCompression(boolean enabled) throws IOException {
579 if (tm != null)
580 throw new IOException("Connection to " + hostname + " is already in connected state!");
581
582 compression = enabled;
583 }
584
585 /**
586 * Close the connection to the SSH-2 server. All assigned sessions will be
587 * closed, too. Can be called at any time. Don't forget to call this once
588 * you don't need a connection anymore - otherwise the receiver thread may
589 * run forever.
590 */
591
592 public synchronized void close() {
593 Throwable t = new Throwable("Closed due to user request.");
594 close(t, false);
595 }
596
597 private void close(Throwable t, boolean hard) {
598 if (cm != null)
599 cm.closeAllChannels();
600
601 if (tm != null) {
602 tm.close(t, hard == false);
603 tm = null;
604 }
605
606 am = null;
607 cm = null;
608 authenticated = false;
609 }
610
611 /**
612 * Same as
613 * {@link #connect(ServerHostKeyVerifier, int, int) connect(null, 0, 0)}.
614 *
615 * @return see comments for the
616 * {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)}
617 * method.
618 * @throws IOException
619 */
620
621 public synchronized ConnectionInfo connect() throws IOException {
622 return connect(null, 0, 0);
623 }
624
625 /**
626 * Same as
627 * {@link #connect(ServerHostKeyVerifier, int, int) connect(verifier, 0, 0)}.
628 *
629 * @return see comments for the
630 * {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)}
631 * method.
632 * @throws IOException
633 */
634
635 public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier) throws IOException {
636 return connect(verifier, 0, 0);
637 }
638
639 /**
640 * Connect to the SSH-2 server and, as soon as the server has presented its
641 * host key, use the
642 * {@link ServerHostKeyVerifier#verifyServerHostKey(String, int, String,
643 * byte[]) ServerHostKeyVerifier.verifyServerHostKey()} method of the
644 * <code>verifier</code> to ask for permission to proceed. If
645 * <code>verifier</code> is <code>null</code>, then any host key will
646 * be accepted - this is NOT recommended, since it makes man-in-the-middle
647 * attackes VERY easy (somebody could put a proxy SSH server between you and
648 * the real server).
649 * <p>
650 * Note: The verifier will be called before doing any crypto calculations
651 * (i.e., diffie-hellman). Therefore, if you don't like the presented host
652 * key then no CPU cycles are wasted (and the evil server has less
653 * information about us).
654 * <p>
655 * However, it is still possible that the server presented a fake host key:
656 * the server cheated (typically a sign for a man-in-the-middle attack) and
657 * is not able to generate a signature that matches its host key. Don't
658 * worry, the library will detect such a scenario later when checking the
659 * signature (the signature cannot be checked before having completed the
660 * diffie-hellman exchange).
661 * <p>
662 * Note 2: The {@link ServerHostKeyVerifier#verifyServerHostKey(String, int,
663 * String, byte[]) ServerHostKeyVerifier.verifyServerHostKey()} method will
664 * *NOT* be called from the current thread, the call is being made from a
665 * background thread (there is a background dispatcher thread for every
666 * established connection).
667 * <p>
668 * Note 3: This method will block as long as the key exchange of the
669 * underlying connection has not been completed (and you have not specified
670 * any timeouts).
671 * <p>
672 * Note 4: If you want to re-use a connection object that was successfully
673 * connected, then you must call the {@link #close()} method before invoking
674 * <code>connect()</code> again.
675 *
676 * @param verifier
677 * An object that implements the {@link ServerHostKeyVerifier}
678 * interface. Pass <code>null</code> to accept any server host
679 * key - NOT recommended.
680 *
681 * @param connectTimeout
682 * Connect the underlying TCP socket to the server with the given
683 * timeout value (non-negative, in milliseconds). Zero means no
684 * timeout. If a proxy is being used (see
685 * {@link #setProxyData(ProxyData)}), then this timeout is used
686 * for the connection establishment to the proxy.
687 *
688 * @param kexTimeout
689 * Timeout for complete connection establishment (non-negative,
690 * in milliseconds). Zero means no timeout. The timeout counts
691 * from the moment you invoke the connect() method and is
692 * cancelled as soon as the first key-exchange round has
693 * finished. It is possible that the timeout event will be fired
694 * during the invocation of the <code>verifier</code> callback,
695 * but it will only have an effect after the
696 * <code>verifier</code> returns.
697 *
698 * @return A {@link ConnectionInfo} object containing the details of the
699 * established connection.
700 *
701 * @throws IOException
702 * If any problem occurs, e.g., the server's host key is not
703 * accepted by the <code>verifier</code> or there is problem
704 * during the initial crypto setup (e.g., the signature sent by
705 * the server is wrong).
706 * <p>
707 * In case of a timeout (either connectTimeout or kexTimeout) a
708 * SocketTimeoutException is thrown.
709 * <p>
710 * An exception may also be thrown if the connection was already
711 * successfully connected (no matter if the connection broke in
712 * the mean time) and you invoke <code>connect()</code> again
713 * without having called {@link #close()} first.
714 * <p>
715 * If a HTTP proxy is being used and the proxy refuses the
716 * connection, then a {@link HTTPProxyException} may be thrown,
717 * which contains the details returned by the proxy. If the
718 * proxy is buggy and does not return a proper HTTP response,
719 * then a normal IOException is thrown instead.
720 */
721
722 public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier, int connectTimeout, int kexTimeout)
723 throws IOException {
724 final class TimeoutState {
725 boolean isCancelled = false;
726 boolean timeoutSocketClosed = false;
727 }
728
729 if (tm != null)
730 throw new IOException("Connection to " + hostname + " is already in connected state!");
731
732 if (connectTimeout < 0)
733 throw new IllegalArgumentException("connectTimeout must be non-negative!");
734
735 if (kexTimeout < 0)
736 throw new IllegalArgumentException("kexTimeout must be non-negative!");
737
738 final TimeoutState state = new TimeoutState();
739 tm = new TransportManager(hostname, port);
740 tm.setConnectionMonitors(connectionMonitors);
741
742 // Don't offer compression if not requested
743 if (!compression) {
744 cryptoWishList.c2s_comp_algos = new String[] { "none" };
745 cryptoWishList.s2c_comp_algos = new String[] { "none" };
746 }
747
748 /*
749 * Make sure that the runnable below will observe the new value of "tm"
750 * and "state" (the runnable will be executed in a different thread,
751 * which may be already running, that is why we need a memory barrier
752 * here). See also the comment in Channel.java if you are interested in
753 * the details.
754 *
755 * OKOK, this is paranoid since adding the runnable to the todo list of
756 * the TimeoutService will ensure that all writes have been flushed
757 * before the Runnable reads anything (there is a synchronized block in
758 * TimeoutService.addTimeoutHandler).
759 */
760
761 synchronized (tm) {
762 /* We could actually synchronize on anything. */
763 }
764
765 try {
766 TimeoutToken token = null;
767
768 if (kexTimeout > 0) {
769 final Runnable timeoutHandler = new Runnable() {
770 public void run() {
771 synchronized (state) {
772 if (state.isCancelled)
773 return;
774
775 state.timeoutSocketClosed = true;
776 tm.close(new SocketTimeoutException("The connect timeout expired"), false);
777 }
778 }
779 };
780 long timeoutHorizont = System.currentTimeMillis() + kexTimeout;
781 token = TimeoutService.addTimeoutHandler(timeoutHorizont, timeoutHandler);
782 }
783
784 try {
785 tm.initialize(cryptoWishList, verifier, dhgexpara, connectTimeout, getOrCreateSecureRND(), proxyData);
786 }
787 catch (SocketTimeoutException se) {
788 throw(SocketTimeoutException) new SocketTimeoutException(
789 "The connect() operation on the socket timed out.").initCause(se);
790 }
791
792 tm.setTcpNoDelay(tcpNoDelay);
793 /* Wait until first KEX has finished */
794 ConnectionInfo ci = tm.getConnectionInfo(1);
795
796 /* Now try to cancel the timeout, if needed */
797
798 if (token != null) {
799 TimeoutService.cancelTimeoutHandler(token);
800
801 /* Were we too late? */
802
803 synchronized (state) {
804 if (state.timeoutSocketClosed)
805 throw new IOException("This exception will be replaced by the one below =)");
806
807 /*
808 * Just in case the "cancelTimeoutHandler" invocation came
809 * just a little bit too late but the handler did not enter
810 * the semaphore yet - we can still stop it.
811 */
812 state.isCancelled = true;
813 }
814 }
815
816 return ci;
817 }
818 catch (SocketTimeoutException ste) {
819 throw ste;
820 }
821 catch (IOException e1) {
822 /* This will also invoke any registered connection monitors */
823 close(new Throwable("There was a problem during connect."), false);
824
825 synchronized (state) {
826 /*
827 * Show a clean exception, not something like "the socket is
828 * closed!?!"
829 */
830 if (state.timeoutSocketClosed)
831 throw new SocketTimeoutException("The kexTimeout (" + kexTimeout + " ms) expired.");
832 }
833
834 /* Do not wrap a HTTPProxyException */
835 if (e1 instanceof HTTPProxyException)
836 throw e1;
837
838 throw(IOException) new IOException("There was a problem while connecting to " + hostname + ":" + port)
839 .initCause(e1);
840 }
841 }
842
843 /**
844 * Creates a new {@link LocalPortForwarder}. A
845 * <code>LocalPortForwarder</code> forwards TCP/IP connections that arrive
846 * at a local port via the secure tunnel to another host (which may or may
847 * not be identical to the remote SSH-2 server).
848 * <p>
849 * This method must only be called after one has passed successfully the
850 * authentication step. There is no limit on the number of concurrent
851 * forwardings.
852 *
853 * @param local_port
854 * the local port the LocalPortForwarder shall bind to.
855 * @param host_to_connect
856 * target address (IP or hostname)
857 * @param port_to_connect
858 * target port
859 * @return A {@link LocalPortForwarder} object.
860 * @throws IOException
861 */
862
863 public synchronized LocalPortForwarder createLocalPortForwarder(int local_port, String host_to_connect,
864 int port_to_connect) throws IOException {
865 if (tm == null)
866 throw new IllegalStateException("Cannot forward ports, you need to establish a connection first.");
867
868 if (!authenticated)
869 throw new IllegalStateException("Cannot forward ports, connection is not authenticated.");
870
871 return new LocalPortForwarder(cm, local_port, host_to_connect, port_to_connect);
872 }
873
874 /**
875 * Creates a new {@link LocalPortForwarder}. A
876 * <code>LocalPortForwarder</code> forwards TCP/IP connections that arrive
877 * at a local port via the secure tunnel to another host (which may or may
878 * not be identical to the remote SSH-2 server).
879 * <p>
880 * This method must only be called after one has passed successfully the
881 * authentication step. There is no limit on the number of concurrent
882 * forwardings.
883 *
884 * @param addr
885 * specifies the InetSocketAddress where the local socket shall
886 * be bound to.
887 * @param host_to_connect
888 * target address (IP or hostname)
889 * @param port_to_connect
890 * target port
891 * @return A {@link LocalPortForwarder} object.
892 * @throws IOException
893 */
894
895 public synchronized LocalPortForwarder createLocalPortForwarder(InetSocketAddress addr, String host_to_connect,
896 int port_to_connect) throws IOException {
897 if (tm == null)
898 throw new IllegalStateException("Cannot forward ports, you need to establish a connection first.");
899
900 if (!authenticated)
901 throw new IllegalStateException("Cannot forward ports, connection is not authenticated.");
902
903 return new LocalPortForwarder(cm, addr, host_to_connect, port_to_connect);
904 }
905
906 /**
907 * Creates a new {@link LocalStreamForwarder}. A
908 * <code>LocalStreamForwarder</code> manages an Input/Outputstream pair
909 * that is being forwarded via the secure tunnel into a TCP/IP connection to
910 * another host (which may or may not be identical to the remote SSH-2
911 * server).
912 *
913 * @param host_to_connect
914 * @param port_to_connect
915 * @return A {@link LocalStreamForwarder} object.
916 * @throws IOException
917 */
918
919 public synchronized LocalStreamForwarder createLocalStreamForwarder(String host_to_connect, int port_to_connect)
920 throws IOException {
921 if (tm == null)
922 throw new IllegalStateException("Cannot forward, you need to establish a connection first.");
923
924 if (!authenticated)
925 throw new IllegalStateException("Cannot forward, connection is not authenticated.");
926
927 return new LocalStreamForwarder(cm, host_to_connect, port_to_connect);
928 }
929
930 /**
931 * Creates a new {@link DynamicPortForwarder}. A
932 * <code>DynamicPortForwarder</code> forwards TCP/IP connections that arrive
933 * at a local port via the secure tunnel to another host that is chosen via
934 * the SOCKS protocol.
935 * <p>
936 * This method must only be called after one has passed successfully the
937 * authentication step. There is no limit on the number of concurrent
938 * forwardings.
939 *
940 * @param local_port
941 * @return A {@link DynamicPortForwarder} object.
942 * @throws IOException
943 */
944
945 public synchronized DynamicPortForwarder createDynamicPortForwarder(int local_port) throws IOException {
946 if (tm == null)
947 throw new IllegalStateException("Cannot forward ports, you need to establish a connection first.");
948
949 if (!authenticated)
950 throw new IllegalStateException("Cannot forward ports, connection is not authenticated.");
951
952 return new DynamicPortForwarder(cm, local_port);
953 }
954
955 /**
956 * Creates a new {@link DynamicPortForwarder}. A
957 * <code>DynamicPortForwarder</code> forwards TCP/IP connections that arrive
958 * at a local port via the secure tunnel to another host that is chosen via
959 * the SOCKS protocol.
960 * <p>
961 * This method must only be called after one has passed successfully the
962 * authentication step. There is no limit on the number of concurrent
963 * forwardings.
964 *
965 * @param addr
966 * specifies the InetSocketAddress where the local socket shall
967 * be bound to.
968 * @return A {@link DynamicPortForwarder} object.
969 * @throws IOException
970 */
971
972 public synchronized DynamicPortForwarder createDynamicPortForwarder(InetSocketAddress addr) throws IOException {
973 if (tm == null)
974 throw new IllegalStateException("Cannot forward ports, you need to establish a connection first.");
975
976 if (!authenticated)
977 throw new IllegalStateException("Cannot forward ports, connection is not authenticated.");
978
979 return new DynamicPortForwarder(cm, addr);
980 }
981
982 /**
983 * Create a very basic {@link SCPClient} that can be used to copy files
984 * from/to the SSH-2 server.
985 * <p>
986 * Works only after one has passed successfully the authentication step.
987 * There is no limit on the number of concurrent SCP clients.
988 * <p>
989 * Note: This factory method will probably disappear in the future.
990 *
991 * @return A {@link SCPClient} object.
992 * @throws IOException
993 */
994
995 public synchronized SCPClient createSCPClient() throws IOException {
996 if (tm == null)
997 throw new IllegalStateException("Cannot create SCP client, you need to establish a connection first.");
998
999 if (!authenticated)
1000 throw new IllegalStateException("Cannot create SCP client, connection is not authenticated.");
1001
1002 return new SCPClient(this);
1003 }
1004
1005 /**
1006 * Force an asynchronous key re-exchange (the call does not block). The
1007 * latest values set for MAC, Cipher and DH group exchange parameters will
1008 * be used. If a key exchange is currently in progress, then this method has
1009 * the only effect that the so far specified parameters will be used for the
1010 * next (server driven) key exchange.
1011 * <p>
1012 * Note: This implementation will never start a key exchange (other than the
1013 * initial one) unless you or the SSH-2 server ask for it.
1014 *
1015 * @throws IOException
1016 * In case of any failure behind the scenes.
1017 */
1018
1019 public synchronized void forceKeyExchange() throws IOException {
1020 if (tm == null)
1021 throw new IllegalStateException("You need to establish a connection first.");
1022
1023 tm.forceKeyExchange(cryptoWishList, dhgexpara);
1024 }
1025
1026 /**
1027 * Returns the hostname that was passed to the constructor.
1028 *
1029 * @return the hostname
1030 */
1031
1032 public synchronized String getHostname() {
1033 return hostname;
1034 }
1035
1036 /**
1037 * Returns the port that was passed to the constructor.
1038 *
1039 * @return the TCP port
1040 */
1041
1042 public synchronized int getPort() {
1043 return port;
1044 }
1045
1046 /**
1047 * Returns a {@link ConnectionInfo} object containing the details of the
1048 * connection. Can be called as soon as the connection has been established
1049 * (successfully connected).
1050 *
1051 * @return A {@link ConnectionInfo} object.
1052 * @throws IOException
1053 * In case of any failure behind the scenes.
1054 */
1055
1056 public synchronized ConnectionInfo getConnectionInfo() throws IOException {
1057 if (tm == null)
1058 throw new IllegalStateException(
1059 "Cannot get details of connection, you need to establish a connection first.");
1060
1061 return tm.getConnectionInfo(1);
1062 }
1063
1064 /**
1065 * After a successful connect, one has to authenticate oneself. This method
1066 * can be used to tell which authentication methods are supported by the
1067 * server at a certain stage of the authentication process (for the given
1068 * username).
1069 * <p>
1070 * Note 1: the username will only be used if no authentication step was done
1071 * so far (it will be used to ask the server for a list of possible
1072 * authentication methods by sending the initial "none" request). Otherwise,
1073 * this method ignores the user name and returns a cached method list (which
1074 * is based on the information contained in the last negative server
1075 * response).
1076 * <p>
1077 * Note 2: the server may return method names that are not supported by this
1078 * implementation.
1079 * <p>
1080 * After a successful authentication, this method must not be called
1081 * anymore.
1082 *
1083 * @param user
1084 * A <code>String</code> holding the username.
1085 *
1086 * @return a (possibly emtpy) array holding authentication method names.
1087 * @throws IOException
1088 */
1089
1090 public synchronized String[] getRemainingAuthMethods(String user) throws IOException {
1091 if (user == null)
1092 throw new IllegalArgumentException("user argument may not be NULL!");
1093
1094 if (tm == null)
1095 throw new IllegalStateException("Connection is not established!");
1096
1097 if (authenticated)
1098 throw new IllegalStateException("Connection is already authenticated!");
1099
1100 if (am == null)
1101 am = new AuthenticationManager(tm);
1102
1103 if (cm == null)
1104 cm = new ChannelManager(tm);
1105
1106 return am.getRemainingMethods(user);
1107 }
1108
1109 /**
1110 * Determines if the authentication phase is complete. Can be called at any
1111 * time.
1112 *
1113 * @return <code>true</code> if no further authentication steps are
1114 * needed.
1115 */
1116
1117 public synchronized boolean isAuthenticationComplete() {
1118 return authenticated;
1119 }
1120
1121 /**
1122 * Returns true if there was at least one failed authentication request and
1123 * the last failed authentication request was marked with "partial success"
1124 * by the server. This is only needed in the rare case of SSH-2 server
1125 * setups that cannot be satisfied with a single successful authentication
1126 * request (i.e., multiple authentication steps are needed.)
1127 * <p>
1128 * If you are interested in the details, then have a look at RFC4252.
1129 *
1130 * @return if the there was a failed authentication step and the last one
1131 * was marked as a "partial success".
1132 */
1133
1134 public synchronized boolean isAuthenticationPartialSuccess() {
1135 if (am == null)
1136 return false;
1137
1138 return am.getPartialSuccess();
1139 }
1140
1141 /**
1142 * Checks if a specified authentication method is available. This method is
1143 * actually just a wrapper for {@link #getRemainingAuthMethods(String)
1144 * getRemainingAuthMethods()}.
1145 *
1146 * @param user
1147 * A <code>String</code> holding the username.
1148 * @param method
1149 * An authentication method name (e.g., "publickey", "password",
1150 * "keyboard-interactive") as specified by the SSH-2 standard.
1151 * @return if the specified authentication method is currently available.
1152 * @throws IOException
1153 */
1154
1155 public synchronized boolean isAuthMethodAvailable(String user, String method) throws IOException {
1156 if (method == null)
1157 throw new IllegalArgumentException("method argument may not be NULL!");
1158
1159 String methods[] = getRemainingAuthMethods(user);
1160
1161 for (int i = 0; i < methods.length; i++) {
1162 if (methods[i].compareTo(method) == 0)
1163 return true;
1164 }
1165
1166 return false;
1167 }
1168
1169 private final SecureRandom getOrCreateSecureRND() {
1170 if (generator == null)
1171 generator = new SecureRandom();
1172
1173 return generator;
1174 }
1175
1176 /**
1177 * Open a new {@link Session} on this connection. Works only after one has
1178 * passed successfully the authentication step. There is no limit on the
1179 * number of concurrent sessions.
1180 *
1181 * @return A {@link Session} object.
1182 * @throws IOException
1183 */
1184
1185 public synchronized Session openSession() throws IOException {
1186 if (tm == null)
1187 throw new IllegalStateException("Cannot open session, you need to establish a connection first.");
1188
1189 if (!authenticated)
1190 throw new IllegalStateException("Cannot open session, connection is not authenticated.");
1191
1192 return new Session(cm, getOrCreateSecureRND());
1193 }
1194
1195 /**
1196 * Send an SSH_MSG_IGNORE packet. This method will generate a random data
1197 * attribute (length between 0 (invlusive) and 16 (exclusive) bytes,
1198 * contents are random bytes).
1199 * <p>
1200 * This method must only be called once the connection is established.
1201 *
1202 * @throws IOException
1203 */
1204
1205 public synchronized void sendIgnorePacket() throws IOException {
1206 SecureRandom rnd = getOrCreateSecureRND();
1207 byte[] data = new byte[rnd.nextInt(16)];
1208 rnd.nextBytes(data);
1209 sendIgnorePacket(data);
1210 }
1211
1212 /**
1213 * Send an SSH_MSG_IGNORE packet with the given data attribute.
1214 * <p>
1215 * This method must only be called once the connection is established.
1216 *
1217 * @throws IOException
1218 */
1219
1220 public synchronized void sendIgnorePacket(byte[] data) throws IOException {
1221 if (data == null)
1222 throw new IllegalArgumentException("data argument must not be null.");
1223
1224 if (tm == null)
1225 throw new IllegalStateException(
1226 "Cannot send SSH_MSG_IGNORE packet, you need to establish a connection first.");
1227
1228 PacketIgnore pi = new PacketIgnore();
1229 pi.setData(data);
1230 tm.sendMessage(pi.getPayload());
1231 }
1232
1233 /**
1234 * Removes duplicates from a String array, keeps only first occurence of
1235 * each element. Does not destroy order of elements; can handle nulls. Uses
1236 * a very efficient O(N^2) algorithm =)
1237 *
1238 * @param list
1239 * a String array.
1240 * @return a cleaned String array.
1241 */
1242 private String[] removeDuplicates(String[] list) {
1243 if ((list == null) || (list.length < 2))
1244 return list;
1245
1246 String[] list2 = new String[list.length];
1247 int count = 0;
1248
1249 for (int i = 0; i < list.length; i++) {
1250 boolean duplicate = false;
1251 String element = list[i];
1252
1253 for (int j = 0; j < count; j++) {
1254 if (((element == null) && (list2[j] == null)) || ((element != null) && (element.equals(list2[j])))) {
1255 duplicate = true;
1256 break;
1257 }
1258 }
1259
1260 if (duplicate)
1261 continue;
1262
1263 list2[count++] = list[i];
1264 }
1265
1266 if (count == list2.length)
1267 return list2;
1268
1269 String[] tmp = new String[count];
1270 System.arraycopy(list2, 0, tmp, 0, count);
1271 return tmp;
1272 }
1273
1274 /**
1275 * Unless you know what you are doing, you will never need this.
1276 *
1277 * @param ciphers
1278 */
1279
1280 public synchronized void setClient2ServerCiphers(String[] ciphers) {
1281 if ((ciphers == null) || (ciphers.length == 0))
1282 throw new IllegalArgumentException();
1283
1284 ciphers = removeDuplicates(ciphers);
1285 BlockCipherFactory.checkCipherList(ciphers);
1286 cryptoWishList.c2s_enc_algos = ciphers;
1287 }
1288
1289 /**
1290 * Unless you know what you are doing, you will never need this.
1291 *
1292 * @param macs
1293 */
1294
1295 public synchronized void setClient2ServerMACs(String[] macs) {
1296 if ((macs == null) || (macs.length == 0))
1297 throw new IllegalArgumentException();
1298
1299 macs = removeDuplicates(macs);
1300 MAC.checkMacList(macs);
1301 cryptoWishList.c2s_mac_algos = macs;
1302 }
1303
1304 /**
1305 * Sets the parameters for the diffie-hellman group exchange. Unless you
1306 * know what you are doing, you will never need this. Default values are
1307 * defined in the {@link DHGexParameters} class.
1308 *
1309 * @param dgp
1310 * {@link DHGexParameters}, non null.
1311 *
1312 */
1313
1314 public synchronized void setDHGexParameters(DHGexParameters dgp) {
1315 if (dgp == null)
1316 throw new IllegalArgumentException();
1317
1318 dhgexpara = dgp;
1319 }
1320
1321 /**
1322 * Unless you know what you are doing, you will never need this.
1323 *
1324 * @param ciphers
1325 */
1326
1327 public synchronized void setServer2ClientCiphers(String[] ciphers) {
1328 if ((ciphers == null) || (ciphers.length == 0))
1329 throw new IllegalArgumentException();
1330
1331 ciphers = removeDuplicates(ciphers);
1332 BlockCipherFactory.checkCipherList(ciphers);
1333 cryptoWishList.s2c_enc_algos = ciphers;
1334 }
1335
1336 /**
1337 * Unless you know what you are doing, you will never need this.
1338 *
1339 * @param macs
1340 */
1341
1342 public synchronized void setServer2ClientMACs(String[] macs) {
1343 if ((macs == null) || (macs.length == 0))
1344 throw new IllegalArgumentException();
1345
1346 macs = removeDuplicates(macs);
1347 MAC.checkMacList(macs);
1348 cryptoWishList.s2c_mac_algos = macs;
1349 }
1350
1351 /**
1352 * Define the set of allowed server host key algorithms to be used for the
1353 * following key exchange operations.
1354 * <p>
1355 * Unless you know what you are doing, you will never need this.
1356 *
1357 * @param algos
1358 * An array of allowed server host key algorithms. SSH-2 defines
1359 * <code>ssh-dss</code> and <code>ssh-rsa</code>. The
1360 * entries of the array must be ordered after preference, i.e.,
1361 * the entry at index 0 is the most preferred one. You must
1362 * specify at least one entry.
1363 */
1364
1365 public synchronized void setServerHostKeyAlgorithms(String[] algos) {
1366 if ((algos == null) || (algos.length == 0))
1367 throw new IllegalArgumentException();
1368
1369 algos = removeDuplicates(algos);
1370 KexManager.checkServerHostkeyAlgorithmsList(algos);
1371 cryptoWishList.serverHostKeyAlgorithms = algos;
1372 }
1373
1374 /**
1375 * Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm) on the
1376 * underlying socket.
1377 * <p>
1378 * Can be called at any time. If the connection has not yet been established
1379 * then the passed value will be stored and set after the socket has been
1380 * set up. The default value that will be used is <code>false</code>.
1381 *
1382 * @param enable
1383 * the argument passed to the <code>Socket.setTCPNoDelay()</code>
1384 * method.
1385 * @throws IOException
1386 */
1387
1388 public synchronized void setTCPNoDelay(boolean enable) throws IOException {
1389 tcpNoDelay = enable;
1390
1391 if (tm != null)
1392 tm.setTcpNoDelay(enable);
1393 }
1394
1395 /**
1396 * Used to tell the library that the connection shall be established through
1397 * a proxy server. It only makes sense to call this method before calling
1398 * the {@link #connect() connect()} method.
1399 * <p>
1400 * At the moment, only HTTP proxies are supported.
1401 * <p>
1402 * Note: This method can be called any number of times. The
1403 * {@link #connect() connect()} method will use the value set in the last
1404 * preceding invocation of this method.
1405 *
1406 * @see HTTPProxyData
1407 *
1408 * @param proxyData
1409 * Connection information about the proxy. If <code>null</code>,
1410 * then no proxy will be used (non surprisingly, this is also the
1411 * default).
1412 */
1413
1414 public synchronized void setProxyData(ProxyData proxyData) {
1415 this.proxyData = proxyData;
1416 }
1417
1418 /**
1419 * Request a remote port forwarding. If successful, then forwarded
1420 * connections will be redirected to the given target address. You can
1421 * cancle a requested remote port forwarding by calling
1422 * {@link #cancelRemotePortForwarding(int) cancelRemotePortForwarding()}.
1423 * <p>
1424 * A call of this method will block until the peer either agreed or
1425 * disagreed to your request-
1426 * <p>
1427 * Note 1: this method typically fails if you
1428 * <ul>
1429 * <li>pass a port number for which the used remote user has not enough
1430 * permissions (i.e., port &lt; 1024)</li>
1431 * <li>or pass a port number that is already in use on the remote server</li>
1432 * <li>or if remote port forwarding is disabled on the server.</li>
1433 * </ul>
1434 * <p>
1435 * Note 2: (from the openssh man page): By default, the listening socket on
1436 * the server will be bound to the loopback interface only. This may be
1437 * overriden by specifying a bind address. Specifying a remote bind address
1438 * will only succeed if the server's <b>GatewayPorts</b> option is enabled
1439 * (see sshd_config(5)).
1440 *
1441 * @param bindAddress
1442 * address to bind to on the server:
1443 * <ul>
1444 * <li>"" means that connections are to be accepted on all
1445 * protocol families supported by the SSH implementation</li>
1446 * <li>"0.0.0.0" means to listen on all IPv4 addresses</li>
1447 * <li>"::" means to listen on all IPv6 addresses</li>
1448 * <li>"localhost" means to listen on all protocol families
1449 * supported by the SSH implementation on loopback addresses
1450 * only, [RFC3330] and RFC3513]</li>
1451 * <li>"127.0.0.1" and "::1" indicate listening on the loopback
1452 * interfaces for IPv4 and IPv6 respectively</li>
1453 * </ul>
1454 * @param bindPort
1455 * port number to bind on the server (must be &gt; 0)
1456 * @param targetAddress
1457 * the target address (IP or hostname)
1458 * @param targetPort
1459 * the target port
1460 * @throws IOException
1461 */
1462
1463 public synchronized void requestRemotePortForwarding(String bindAddress, int bindPort, String targetAddress,
1464 int targetPort) throws IOException {
1465 if (tm == null)
1466 throw new IllegalStateException("You need to establish a connection first.");
1467
1468 if (!authenticated)
1469 throw new IllegalStateException("The connection is not authenticated.");
1470
1471 if ((bindAddress == null) || (targetAddress == null) || (bindPort <= 0) || (targetPort <= 0))
1472 throw new IllegalArgumentException();
1473
1474 cm.requestGlobalForward(bindAddress, bindPort, targetAddress, targetPort);
1475 }
1476
1477 /**
1478 * Cancel an earlier requested remote port forwarding. Currently active
1479 * forwardings will not be affected (e.g., disrupted). Note that further
1480 * connection forwarding requests may be received until this method has
1481 * returned.
1482 *
1483 * @param bindPort
1484 * the allocated port number on the server
1485 * @throws IOException
1486 * if the remote side refuses the cancel request or another low
1487 * level error occurs (e.g., the underlying connection is
1488 * closed)
1489 */
1490
1491 public synchronized void cancelRemotePortForwarding(int bindPort) throws IOException {
1492 if (tm == null)
1493 throw new IllegalStateException("You need to establish a connection first.");
1494
1495 if (!authenticated)
1496 throw new IllegalStateException("The connection is not authenticated.");
1497
1498 cm.requestCancelGlobalForward(bindPort);
1499 }
1500
1501 /**
1502 * Provide your own instance of SecureRandom. Can be used, e.g., if you want
1503 * to seed the used SecureRandom generator manually.
1504 * <p>
1505 * The SecureRandom instance is used during key exchanges, public key
1506 * authentication, x11 cookie generation and the like.
1507 *
1508 * @param rnd
1509 * a SecureRandom instance
1510 */
1511
1512 public synchronized void setSecureRandom(SecureRandom rnd) {
1513 if (rnd == null)
1514 throw new IllegalArgumentException();
1515
1516 this.generator = rnd;
1517 }
1518
1519 /**
1520 * Enable/disable debug logging. <b>Only do this when requested by Trilead
1521 * support.</b>
1522 * <p>
1523 * For speed reasons, some static variables used to check whether debugging
1524 * is enabled are not protected with locks. In other words, if you
1525 * dynamicaly enable/disable debug logging, then some threads may still use
1526 * the old setting. To be on the safe side, enable debugging before doing
1527 * the <code>connect()</code> call.
1528 *
1529 * @param enable
1530 * on/off
1531 * @param logger
1532 * a {@link DebugLogger DebugLogger} instance, <code>null</code>
1533 * means logging using the simple logger which logs all messages
1534 * to to stderr. Ignored if enabled is <code>false</code>
1535 */
1536
1537 public synchronized void enableDebugging(boolean enable, DebugLogger logger) {
1538 Logger.enabled = enable;
1539
1540 if (enable == false) {
1541 Logger.logger = null;
1542 }
1543 else {
1544 if (logger == null) {
1545 logger = new DebugLogger() {
1546 public void log(int level, String className, String message) {
1547 long now = System.currentTimeMillis();
1548 System.err.println(now + " : " + className + ": " + message);
1549 }
1550 };
1551 }
1552
1553 Logger.logger = logger;
1554 }
1555 }
1556
1557 /**
1558 * This method can be used to perform end-to-end connection testing. It
1559 * sends a 'ping' message to the server and waits for the 'pong' from the
1560 * server.
1561 * <p>
1562 * When this method throws an exception, then you can assume that the
1563 * connection should be abandoned.
1564 * <p>
1565 * Note: Works only after one has passed successfully the authentication
1566 * step.
1567 * <p>
1568 * Implementation details: this method sends a SSH_MSG_GLOBAL_REQUEST
1569 * request ('trilead-ping') to the server and waits for the
1570 * SSH_MSG_REQUEST_FAILURE reply packet from the server.
1571 *
1572 * @throws IOException
1573 * in case of any problem
1574 */
1575
1576 public synchronized void ping() throws IOException {
1577 if (tm == null)
1578 throw new IllegalStateException("You need to establish a connection first.");
1579
1580 if (!authenticated)
1581 throw new IllegalStateException("The connection is not authenticated.");
1582
1583 cm.requestGlobalTrileadPing();
1584 }
1585 }