0
|
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 < 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 > 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 }
|