0001: package ch.ethz.ssh2;
0002:
0003: import java.io.CharArrayWriter;
0004: import java.io.File;
0005: import java.io.FileReader;
0006: import java.io.IOException;
0007: import java.net.SocketTimeoutException;
0008: import java.security.SecureRandom;
0009: import java.util.Vector;
0010:
0011: import ch.ethz.ssh2.auth.AuthenticationManager;
0012: import ch.ethz.ssh2.channel.ChannelManager;
0013: import ch.ethz.ssh2.crypto.CryptoWishList;
0014: import ch.ethz.ssh2.crypto.cipher.BlockCipherFactory;
0015: import ch.ethz.ssh2.crypto.digest.MAC;
0016: import ch.ethz.ssh2.transport.KexManager;
0017: import ch.ethz.ssh2.transport.TransportManager;
0018: import ch.ethz.ssh2.util.TimeoutService;
0019: import ch.ethz.ssh2.util.TimeoutService.TimeoutToken;
0020:
0021: /**
0022: * A <code>Connection</code> is used to establish an encrypted TCP/IP
0023: * connection to a SSH-2 server.
0024: * <p>
0025: * Typically, one
0026: * <ol>
0027: * <li>creates a {@link #Connection(String) Connection} object.</li>
0028: * <li>calls the {@link #connect() connect()} method.</li>
0029: * <li>calls some of the authentication methods (e.g., {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()}).</li>
0030: * <li>calls one or several times the {@link #openSession() openSession()} method.</li>
0031: * <li>finally, one must close the connection and release resources with the {@link #close() close()} method.</li>
0032: * </ol>
0033: *
0034: * @author Christian Plattner, plattner@inf.ethz.ch
0035: * @version $Id: Connection.java,v 1.28 2006/09/12 15:35:26 cplattne Exp $
0036: */
0037:
0038: public class Connection {
0039: /**
0040: * The identifier presented to the SSH-2 server.
0041: */
0042: public final static String identification = "Ganymed Build_210";
0043:
0044: /* Will be used to generate all random data needed for the current connection.
0045: * Note: SecureRandom.nextBytes() is thread safe.
0046: */
0047:
0048: private SecureRandom generator;
0049:
0050: /**
0051: * Unless you know what you are doing, you will never need this.
0052: *
0053: * @return The list of supported cipher algorithms by this implementation.
0054: */
0055: public static synchronized String[] getAvailableCiphers() {
0056: return BlockCipherFactory.getDefaultCipherList();
0057: }
0058:
0059: /**
0060: * Unless you know what you are doing, you will never need this.
0061: *
0062: * @return The list of supported MAC algorthims by this implementation.
0063: */
0064: public static synchronized String[] getAvailableMACs() {
0065: return MAC.getMacList();
0066: }
0067:
0068: /**
0069: * Unless you know what you are doing, you will never need this.
0070: *
0071: * @return The list of supported server host key algorthims by this implementation.
0072: */
0073: public static synchronized String[] getAvailableServerHostKeyAlgorithms() {
0074: return KexManager.getDefaultServerHostkeyAlgorithmList();
0075: }
0076:
0077: private AuthenticationManager am;
0078:
0079: private boolean authenticated = false;
0080: private ChannelManager cm;
0081:
0082: private CryptoWishList cryptoWishList = new CryptoWishList();
0083:
0084: private DHGexParameters dhgexpara = new DHGexParameters();
0085:
0086: private final String hostname;
0087:
0088: private final int port;
0089:
0090: private TransportManager tm;
0091:
0092: private boolean tcpNoDelay = false;
0093:
0094: private ProxyData proxyData = null;
0095:
0096: private Vector connectionMonitors = new Vector();
0097:
0098: /**
0099: * Prepares a fresh <code>Connection</code> object which can then be used
0100: * to establish a connection to the specified SSH-2 server.
0101: * <p>
0102: * Same as {@link #Connection(String, int) Connection(hostname, 22)}.
0103: *
0104: * @param hostname the hostname of the SSH-2 server.
0105: */
0106: public Connection(String hostname) {
0107: this (hostname, 22);
0108: }
0109:
0110: /**
0111: * Prepares a fresh <code>Connection</code> object which can then be used
0112: * to establish a connection to the specified SSH-2 server.
0113: *
0114: * @param hostname
0115: * the host where we later want to connect to.
0116: * @param port
0117: * port on the server, normally 22.
0118: */
0119: public Connection(String hostname, int port) {
0120: this .hostname = hostname;
0121: this .port = port;
0122: }
0123:
0124: /**
0125: * After a successful connect, one has to authenticate oneself. This method
0126: * is based on DSA (it uses DSA to sign a challenge sent by the server).
0127: * <p>
0128: * If the authentication phase is complete, <code>true</code> will be
0129: * returned. If the server does not accept the request (or if further
0130: * authentication steps are needed), <code>false</code> is returned and
0131: * one can retry either by using this or any other authentication method
0132: * (use the <code>getRemainingAuthMethods</code> method to get a list of
0133: * the remaining possible methods).
0134: *
0135: * @param user
0136: * A <code>String</code> holding the username.
0137: * @param pem
0138: * A <code>String</code> containing the DSA private key of the
0139: * user in OpenSSH key format (PEM, you can't miss the
0140: * "-----BEGIN DSA PRIVATE KEY-----" tag). The string may contain
0141: * linefeeds.
0142: * @param password
0143: * If the PEM string is 3DES encrypted ("DES-EDE3-CBC"), then you
0144: * must specify the password. Otherwise, this argument will be
0145: * ignored and can be set to <code>null</code>.
0146: *
0147: * @return whether the connection is now authenticated.
0148: * @throws IOException
0149: *
0150: * @deprecated You should use one of the {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()}
0151: * methods, this method is just a wrapper for it and will
0152: * disappear in future builds.
0153: *
0154: */
0155: public synchronized boolean authenticateWithDSA(String user,
0156: String pem, String password) throws IOException {
0157: if (tm == null)
0158: throw new IllegalStateException(
0159: "Connection is not established!");
0160:
0161: if (authenticated)
0162: throw new IllegalStateException(
0163: "Connection is already authenticated!");
0164:
0165: if (am == null)
0166: am = new AuthenticationManager(tm);
0167:
0168: if (cm == null)
0169: cm = new ChannelManager(tm);
0170:
0171: if (user == null)
0172: throw new IllegalArgumentException("user argument is null");
0173:
0174: if (pem == null)
0175: throw new IllegalArgumentException("pem argument is null");
0176:
0177: authenticated = am.authenticatePublicKey(user, pem
0178: .toCharArray(), password, getOrCreateSecureRND());
0179:
0180: return authenticated;
0181: }
0182:
0183: /**
0184: * A wrapper that calls {@link #authenticateWithKeyboardInteractive(String, String[], InteractiveCallback)
0185: * authenticateWithKeyboardInteractivewith} a <code>null</code> submethod list.
0186: *
0187: * @param user
0188: * A <code>String</code> holding the username.
0189: * @param cb
0190: * An <code>InteractiveCallback</code> which will be used to
0191: * determine the responses to the questions asked by the server.
0192: * @return whether the connection is now authenticated.
0193: * @throws IOException
0194: */
0195: public synchronized boolean authenticateWithKeyboardInteractive(
0196: String user, InteractiveCallback cb) throws IOException {
0197: return authenticateWithKeyboardInteractive(user, null, cb);
0198: }
0199:
0200: /**
0201: * After a successful connect, one has to authenticate oneself. This method
0202: * is based on "keyboard-interactive", specified in
0203: * draft-ietf-secsh-auth-kbdinteract-XX. Basically, you have to define a
0204: * callback object which will be feeded with challenges generated by the
0205: * server. Answers are then sent back to the server. It is possible that the
0206: * callback will be called several times during the invocation of this
0207: * method (e.g., if the server replies to the callback's answer(s) with
0208: * another challenge...)
0209: * <p>
0210: * If the authentication phase is complete, <code>true</code> will be
0211: * returned. If the server does not accept the request (or if further
0212: * authentication steps are needed), <code>false</code> is returned and
0213: * one can retry either by using this or any other authentication method
0214: * (use the <code>getRemainingAuthMethods</code> method to get a list of
0215: * the remaining possible methods).
0216: * <p>
0217: * Note: some SSH servers advertise "keyboard-interactive", however, any
0218: * interactive request will be denied (without having sent any challenge to
0219: * the client).
0220: *
0221: * @param user
0222: * A <code>String</code> holding the username.
0223: * @param submethods
0224: * An array of submethod names, see
0225: * draft-ietf-secsh-auth-kbdinteract-XX. May be <code>null</code>
0226: * to indicate an empty list.
0227: * @param cb
0228: * An <code>InteractiveCallback</code> which will be used to
0229: * determine the responses to the questions asked by the server.
0230: *
0231: * @return whether the connection is now authenticated.
0232: * @throws IOException
0233: */
0234: public synchronized boolean authenticateWithKeyboardInteractive(
0235: String user, String[] submethods, InteractiveCallback cb)
0236: throws IOException {
0237: if (cb == null)
0238: throw new IllegalArgumentException(
0239: "Callback may not ne NULL!");
0240:
0241: if (tm == null)
0242: throw new IllegalStateException(
0243: "Connection is not established!");
0244:
0245: if (authenticated)
0246: throw new IllegalStateException(
0247: "Connection is already authenticated!");
0248:
0249: if (am == null)
0250: am = new AuthenticationManager(tm);
0251:
0252: if (cm == null)
0253: cm = new ChannelManager(tm);
0254:
0255: if (user == null)
0256: throw new IllegalArgumentException("user argument is null");
0257:
0258: authenticated = am
0259: .authenticateInteractive(user, submethods, cb);
0260:
0261: return authenticated;
0262: }
0263:
0264: /**
0265: * After a successfull connect, one has to authenticate oneself. This method
0266: * sends username and password to the server.
0267: * <p>
0268: * If the authentication phase is complete, <code>true</code> will be
0269: * returned. If the server does not accept the request (or if further
0270: * authentication steps are needed), <code>false</code> is returned and
0271: * one can retry either by using this or any other authentication method
0272: * (use the <code>getRemainingAuthMethods</code> method to get a list of
0273: * the remaining possible methods).
0274: * <p>
0275: * Note: if this method fails, then please double-check that it is actually
0276: * offered by the server (use {@link #getRemainingAuthMethods(String) getRemainingAuthMethods()}.
0277: * <p>
0278: * Often, password authentication is disabled, but users are not aware of it.
0279: * Many servers only offer "publickey" and "keyboard-interactive". However,
0280: * even though "keyboard-interactive" *feels* like password authentication
0281: * (e.g., when using the putty or openssh clients) it is *not* the same mechanism.
0282: *
0283: * @param user
0284: * @param password
0285: * @return if the connection is now authenticated.
0286: * @throws IOException
0287: */
0288: public synchronized boolean authenticateWithPassword(String user,
0289: String password) throws IOException {
0290: if (tm == null)
0291: throw new IllegalStateException(
0292: "Connection is not established!");
0293:
0294: if (authenticated)
0295: throw new IllegalStateException(
0296: "Connection is already authenticated!");
0297:
0298: if (am == null)
0299: am = new AuthenticationManager(tm);
0300:
0301: if (cm == null)
0302: cm = new ChannelManager(tm);
0303:
0304: if (user == null)
0305: throw new IllegalArgumentException("user argument is null");
0306:
0307: if (password == null)
0308: throw new IllegalArgumentException(
0309: "password argument is null");
0310:
0311: authenticated = am.authenticatePassword(user, password);
0312:
0313: return authenticated;
0314: }
0315:
0316: /**
0317: * After a successful connect, one has to authenticate oneself.
0318: * The authentication method "publickey" works by signing a challenge
0319: * sent by the server. The signature is either DSA or RSA based - it
0320: * just depends on the type of private key you specify, either a DSA
0321: * or RSA private key in PEM format. And yes, this is may seem to be a
0322: * little confusing, the method is called "publickey" in the SSH-2 protocol
0323: * specification, however since we need to generate a signature, you
0324: * actually have to supply a private key =).
0325: * <p>
0326: * The private key contained in the PEM file may also be encrypted ("Proc-Type: 4,ENCRYPTED").
0327: * The library supports DES-CBC and DES-EDE3-CBC encryption, as well
0328: * as the more exotic PEM encrpytions AES-128-CBC, AES-192-CBC and AES-256-CBC.
0329: * <p>
0330: * If the authentication phase is complete, <code>true</code> will be
0331: * returned. If the server does not accept the request (or if further
0332: * authentication steps are needed), <code>false</code> is returned and
0333: * one can retry either by using this or any other authentication method
0334: * (use the <code>getRemainingAuthMethods</code> method to get a list of
0335: * the remaining possible methods).
0336: * <p>
0337: * NOTE PUTTY USERS: Event though your key file may start with "-----BEGIN..."
0338: * it is not in the expected format. You have to convert it to the OpenSSH
0339: * key format by using the "puttygen" tool (can be downloaded from the Putty
0340: * website). Simply load your key and then use the "Conversions/Export OpenSSH key"
0341: * functionality to get a proper PEM file.
0342: *
0343: * @param user
0344: * A <code>String</code> holding the username.
0345: * @param pemPrivateKey
0346: * A <code>char[]</code> containing a DSA or RSA private key of the
0347: * user in OpenSSH key format (PEM, you can't miss the
0348: * "-----BEGIN DSA PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE KEY-----"
0349: * tag). The char array may contain linebreaks/linefeeds.
0350: * @param password
0351: * If the PEM structure is encrypted ("Proc-Type: 4,ENCRYPTED") then
0352: * you must specify a password. Otherwise, this argument will be ignored
0353: * and can be set to <code>null</code>.
0354: *
0355: * @return whether the connection is now authenticated.
0356: * @throws IOException
0357: */
0358: public synchronized boolean authenticateWithPublicKey(String user,
0359: char[] pemPrivateKey, String password) throws IOException {
0360: if (tm == null)
0361: throw new IllegalStateException(
0362: "Connection is not established!");
0363:
0364: if (authenticated)
0365: throw new IllegalStateException(
0366: "Connection is already authenticated!");
0367:
0368: if (am == null)
0369: am = new AuthenticationManager(tm);
0370:
0371: if (cm == null)
0372: cm = new ChannelManager(tm);
0373:
0374: if (user == null)
0375: throw new IllegalArgumentException("user argument is null");
0376:
0377: if (pemPrivateKey == null)
0378: throw new IllegalArgumentException(
0379: "pemPrivateKey argument is null");
0380:
0381: authenticated = am.authenticatePublicKey(user, pemPrivateKey,
0382: password, getOrCreateSecureRND());
0383:
0384: return authenticated;
0385: }
0386:
0387: /**
0388: * A convenience wrapper function which reads in a private key (PEM format, either DSA or RSA)
0389: * and then calls <code>authenticateWithPublicKey(String, char[], String)</code>.
0390: * <p>
0391: * NOTE PUTTY USERS: Event though your key file may start with "-----BEGIN..."
0392: * it is not in the expected format. You have to convert it to the OpenSSH
0393: * key format by using the "puttygen" tool (can be downloaded from the Putty
0394: * website). Simply load your key and then use the "Conversions/Export OpenSSH key"
0395: * functionality to get a proper PEM file.
0396: *
0397: * @param user
0398: * A <code>String</code> holding the username.
0399: * @param pemFile
0400: * A <code>File</code> object pointing to a file containing a DSA or RSA
0401: * private key of the user in OpenSSH key format (PEM, you can't miss the
0402: * "-----BEGIN DSA PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE KEY-----"
0403: * tag).
0404: * @param password
0405: * If the PEM file is encrypted then you must specify the password.
0406: * Otherwise, this argument will be ignored and can be set to <code>null</code>.
0407: *
0408: * @return whether the connection is now authenticated.
0409: * @throws IOException
0410: */
0411: public synchronized boolean authenticateWithPublicKey(String user,
0412: File pemFile, String password) throws IOException {
0413: if (pemFile == null)
0414: throw new IllegalArgumentException(
0415: "pemFile argument is null");
0416:
0417: char[] buff = new char[256];
0418:
0419: CharArrayWriter cw = new CharArrayWriter();
0420:
0421: FileReader fr = new FileReader(pemFile);
0422:
0423: while (true) {
0424: int len = fr.read(buff);
0425: if (len < 0)
0426: break;
0427: cw.write(buff, 0, len);
0428: }
0429:
0430: fr.close();
0431:
0432: return authenticateWithPublicKey(user, cw.toCharArray(),
0433: password);
0434: }
0435:
0436: /**
0437: * Add a {@link ConnectionMonitor} to this connection. Can be invoked at any time,
0438: * but it is best to add connection monitors before invoking
0439: * <code>connect()</code> to avoid glitches (e.g., you add a connection monitor after
0440: * a successful connect(), but the connection has died in the mean time. Then,
0441: * your connection monitor won't be notified.)
0442: * <p>
0443: * You can add as many monitors as you like.
0444: *
0445: * @see ConnectionMonitor
0446: *
0447: * @param cmon An object implementing the <code>ConnectionMonitor</code> interface.
0448: */
0449: public synchronized void addConnectionMonitor(ConnectionMonitor cmon) {
0450: if (cmon == null)
0451: throw new IllegalArgumentException("cmon argument is null");
0452:
0453: connectionMonitors.addElement(cmon);
0454:
0455: if (tm != null)
0456: tm.setConnectionMonitors(connectionMonitors);
0457: }
0458:
0459: /**
0460: * Close the connection to the SSH-2 server. All assigned sessions will be
0461: * closed, too. Can be called at any time. Don't forget to call this once
0462: * you don't need a connection anymore - otherwise the receiver thread may
0463: * run forever.
0464: */
0465: public synchronized void close() {
0466: Throwable t = new Throwable("Closed due to user request.");
0467: close(t, false);
0468: }
0469:
0470: private void close(Throwable t, boolean hard) {
0471: if (cm != null)
0472: cm.closeAllChannels();
0473:
0474: if (tm != null) {
0475: tm.close(t, hard == false);
0476: tm = null;
0477: }
0478: am = null;
0479: cm = null;
0480: authenticated = false;
0481: }
0482:
0483: /**
0484: * Same as {@link #connect(ServerHostKeyVerifier, int, int) connect(null, 0, 0)}.
0485: *
0486: * @return see comments for the {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)} method.
0487: * @throws IOException
0488: */
0489: public synchronized ConnectionInfo connect() throws IOException {
0490: return connect(null, 0, 0);
0491: }
0492:
0493: /**
0494: * Same as {@link #connect(ServerHostKeyVerifier, int, int) connect(verifier, 0, 0)}.
0495: *
0496: * @return see comments for the {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)} method.
0497: * @throws IOException
0498: */
0499: public synchronized ConnectionInfo connect(
0500: ServerHostKeyVerifier verifier) throws IOException {
0501: return connect(verifier, 0, 0);
0502: }
0503:
0504: /**
0505: * Connect to the SSH-2 server and, as soon as the server has presented its
0506: * host key, use the {@link ServerHostKeyVerifier#verifyServerHostKey(String,
0507: * int, String, byte[]) ServerHostKeyVerifier.verifyServerHostKey()}
0508: * method of the <code>verifier</code> to ask for permission to proceed.
0509: * If <code>verifier</code> is <code>null</code>, then any host key will be
0510: * accepted - this is NOT recommended, since it makes man-in-the-middle attackes
0511: * VERY easy (somebody could put a proxy SSH server between you and the real server).
0512: * <p>
0513: * Note: The verifier will be called before doing any crypto calculations
0514: * (i.e., diffie-hellman). Therefore, if you don't like the presented host key then
0515: * no CPU cycles are wasted (and the evil server has less information about us).
0516: * <p>
0517: * However, it is still possible that the server presented a fake host key: the server
0518: * cheated (typically a sign for a man-in-the-middle attack) and is not able to generate
0519: * a signature that matches its host key. Don't worry, the library will detect such
0520: * a scenario later when checking the signature (the signature cannot be checked before
0521: * having completed the diffie-hellman exchange).
0522: * <p>
0523: * Note 2: The {@link ServerHostKeyVerifier#verifyServerHostKey(String,
0524: * int, String, byte[]) ServerHostKeyVerifier.verifyServerHostKey()} method
0525: * will *NOT* be called from the current thread, the call is being made from a
0526: * background thread (there is a background dispatcher thread for every
0527: * established connection).
0528: * <p>
0529: * Note 3: This method will block as long as the key exchange of the underlying connection
0530: * has not been completed (and you have not specified any timeouts).
0531: * <p>
0532: * Note 4: If you want to re-use a connection object that was successfully connected,
0533: * then you must call the {@link #close()} method before invoking <code>connect()</code> again.
0534: *
0535: * @param verifier
0536: * An object that implements the
0537: * {@link ServerHostKeyVerifier} interface. Pass <code>null</code>
0538: * to accept any server host key - NOT recommended.
0539: *
0540: * @param connectTimeout
0541: * Connect the underlying TCP socket to the server with the given timeout
0542: * value (non-negative, in milliseconds). Zero means no timeout. If a proxy is being
0543: * used (see {@link #setProxyData(ProxyData)}), then this timeout is used for the
0544: * connection establishment to the proxy.
0545: *
0546: * @param kexTimeout
0547: * Timeout for complete connection establishment (non-negative,
0548: * in milliseconds). Zero means no timeout. The timeout counts from the
0549: * moment you invoke the connect() method and is cancelled as soon as the
0550: * first key-exchange round has finished. It is possible that
0551: * the timeout event will be fired during the invocation of the
0552: * <code>verifier</code> callback, but it will only have an effect after
0553: * the <code>verifier</code> returns.
0554: *
0555: * @return A {@link ConnectionInfo} object containing the details of
0556: * the established connection.
0557: *
0558: * @throws IOException
0559: * If any problem occurs, e.g., the server's host key is not
0560: * accepted by the <code>verifier</code> or there is problem during
0561: * the initial crypto setup (e.g., the signature sent by the server is wrong).
0562: * <p>
0563: * In case of a timeout (either connectTimeout or kexTimeout)
0564: * a SocketTimeoutException is thrown.
0565: * <p>
0566: * An exception may also be thrown if the connection was already successfully
0567: * connected (no matter if the connection broke in the mean time) and you invoke
0568: * <code>connect()</code> again without having called {@link #close()} first.
0569: * <p>
0570: * If a HTTP proxy is being used and the proxy refuses the connection,
0571: * then a {@link HTTPProxyException} may be thrown, which
0572: * contains the details returned by the proxy. If the proxy is buggy and does
0573: * not return a proper HTTP response, then a normal IOException is thrown instead.
0574: */
0575: public synchronized ConnectionInfo connect(
0576: ServerHostKeyVerifier verifier, int connectTimeout,
0577: int kexTimeout) throws IOException {
0578: final class TimeoutState {
0579: boolean isCancelled = false;
0580: boolean timeoutSocketClosed = false;
0581: }
0582:
0583: if (tm != null)
0584: throw new IOException("Connection to " + hostname
0585: + " is already in connected state!");
0586:
0587: if (connectTimeout < 0)
0588: throw new IllegalArgumentException(
0589: "connectTimeout must be non-negative!");
0590:
0591: if (kexTimeout < 0)
0592: throw new IllegalArgumentException(
0593: "kexTimeout must be non-negative!");
0594:
0595: final TimeoutState state = new TimeoutState();
0596:
0597: tm = new TransportManager(hostname, port);
0598:
0599: tm.setConnectionMonitors(connectionMonitors);
0600:
0601: /* Make sure that the runnable below will observe the new value of "tm"
0602: * and "state" (the runnable will be executed in a different thread, which
0603: * may be already running, that is why we need a memory barrier here).
0604: * See also the comment in Channel.java if you
0605: * are interested in the details.
0606: *
0607: * OKOK, this is paranoid since adding the runnable to the todo list
0608: * of the TimeoutService will ensure that all writes have been flushed
0609: * before the Runnable reads anything
0610: * (there is a synchronized block in TimeoutService.addTimeoutHandler).
0611: */
0612:
0613: synchronized (tm) {
0614: /* We could actually synchronize on anything. */
0615: }
0616:
0617: try {
0618: TimeoutToken token = null;
0619:
0620: if (kexTimeout > 0) {
0621: final Runnable timeoutHandler = new Runnable() {
0622: public void run() {
0623: synchronized (state) {
0624: if (state.isCancelled)
0625: return;
0626: state.timeoutSocketClosed = true;
0627: tm.close(new SocketTimeoutException(
0628: "The connect timeout expired"),
0629: false);
0630: }
0631: }
0632: };
0633:
0634: long timeoutHorizont = System.currentTimeMillis()
0635: + kexTimeout;
0636:
0637: token = TimeoutService.addTimeoutHandler(
0638: timeoutHorizont, timeoutHandler);
0639: }
0640:
0641: try {
0642: tm.initialize(cryptoWishList, verifier, dhgexpara,
0643: connectTimeout, getOrCreateSecureRND(),
0644: proxyData);
0645: } catch (SocketTimeoutException se) {
0646: throw (SocketTimeoutException) new SocketTimeoutException(
0647: "The connect() operation on the socket timed out.")
0648: .initCause(se);
0649: }
0650:
0651: tm.setTcpNoDelay(tcpNoDelay);
0652:
0653: /* Wait until first KEX has finished */
0654:
0655: ConnectionInfo ci = tm.getConnectionInfo(1);
0656:
0657: /* Now try to cancel the timeout, if needed */
0658:
0659: if (token != null) {
0660: TimeoutService.cancelTimeoutHandler(token);
0661:
0662: /* Were we too late? */
0663:
0664: synchronized (state) {
0665: if (state.timeoutSocketClosed)
0666: throw new IOException(
0667: "This exception will be replaced by the one below =)");
0668: /* Just in case the "cancelTimeoutHandler" invocation came just a little bit
0669: * too late but the handler did not enter the semaphore yet - we can
0670: * still stop it.
0671: */
0672: state.isCancelled = true;
0673: }
0674: }
0675:
0676: return ci;
0677: } catch (SocketTimeoutException ste) {
0678: throw ste;
0679: } catch (IOException e1) {
0680: /* This will also invoke any registered connection monitors */
0681: close(new Throwable("There was a problem during connect."),
0682: false);
0683:
0684: synchronized (state) {
0685: /* Show a clean exception, not something like "the socket is closed!?!" */
0686: if (state.timeoutSocketClosed)
0687: throw new SocketTimeoutException("The kexTimeout ("
0688: + kexTimeout + " ms) expired.");
0689: }
0690:
0691: /* Do not wrap a HTTPProxyException */
0692: if (e1 instanceof HTTPProxyException)
0693: throw e1;
0694:
0695: throw (IOException) new IOException(
0696: "There was a problem while connecting to "
0697: + hostname + ":" + port).initCause(e1);
0698: }
0699: }
0700:
0701: /**
0702: * Creates a new {@link LocalPortForwarder}.
0703: * A <code>LocalPortForwarder</code> forwards TCP/IP connections that arrive at a local
0704: * port via the secure tunnel to another host (which may or may not be
0705: * identical to the remote SSH-2 server).
0706: * <p>
0707: * This method must only be called after one has passed successfully the authentication step.
0708: * There is no limit on the number of concurrent forwardings.
0709: *
0710: * @param local_port the local port the LocalPortForwarder shall bind to.
0711: * @param host_to_connect target address (IP or hostname)
0712: * @param port_to_connect target port
0713: * @return A {@link LocalPortForwarder} object.
0714: * @throws IOException
0715: */
0716: public synchronized LocalPortForwarder createLocalPortForwarder(
0717: int local_port, String host_to_connect, int port_to_connect)
0718: throws IOException {
0719: if (tm == null)
0720: throw new IllegalStateException(
0721: "Cannot forward ports, you need to establish a connection first.");
0722:
0723: if (!authenticated)
0724: throw new IllegalStateException(
0725: "Cannot forward ports, connection is not authenticated.");
0726:
0727: return new LocalPortForwarder(cm, local_port, host_to_connect,
0728: port_to_connect);
0729: }
0730:
0731: /**
0732: * Creates a new {@link LocalStreamForwarder}.
0733: * A <code>LocalStreamForwarder</code> manages an Input/Outputstream pair
0734: * that is being forwarded via the secure tunnel into a TCP/IP connection to another host
0735: * (which may or may not be identical to the remote SSH-2 server).
0736: *
0737: * @param host_to_connect
0738: * @param port_to_connect
0739: * @return A {@link LocalStreamForwarder} object.
0740: * @throws IOException
0741: */
0742: public synchronized LocalStreamForwarder createLocalStreamForwarder(
0743: String host_to_connect, int port_to_connect)
0744: throws IOException {
0745: if (tm == null)
0746: throw new IllegalStateException(
0747: "Cannot forward, you need to establish a connection first.");
0748:
0749: if (!authenticated)
0750: throw new IllegalStateException(
0751: "Cannot forward, connection is not authenticated.");
0752:
0753: return new LocalStreamForwarder(cm, host_to_connect,
0754: port_to_connect);
0755: }
0756:
0757: /**
0758: * Create a very basic {@link SCPClient} that can be used to copy
0759: * files from/to the SSH-2 server.
0760: * <p>
0761: * Works only after one has passed successfully the authentication step.
0762: * There is no limit on the number of concurrent SCP clients.
0763: * <p>
0764: * Note: This factory method will probably disappear in the future.
0765: *
0766: * @return A {@link SCPClient} object.
0767: * @throws IOException
0768: */
0769: public synchronized SCPClient createSCPClient() throws IOException {
0770: if (tm == null)
0771: throw new IllegalStateException(
0772: "Cannot create SCP client, you need to establish a connection first.");
0773:
0774: if (!authenticated)
0775: throw new IllegalStateException(
0776: "Cannot create SCP client, connection is not authenticated.");
0777:
0778: return new SCPClient(this );
0779: }
0780:
0781: /**
0782: * Force an asynchronous key re-exchange (the call does not block). The
0783: * latest values set for MAC, Cipher and DH group exchange parameters will
0784: * be used. If a key exchange is currently in progress, then this method has
0785: * the only effect that the so far specified parameters will be used for the
0786: * next (server driven) key exchange.
0787: * <p>
0788: * Note: This implementation will never start a key exchange (other than the initial one)
0789: * unless you or the SSH-2 server ask for it.
0790: *
0791: * @throws IOException
0792: * In case of any failure behind the scenes.
0793: */
0794: public synchronized void forceKeyExchange() throws IOException {
0795: if (tm == null)
0796: throw new IllegalStateException(
0797: "You need to establish a connection first.");
0798:
0799: tm.forceKeyExchange(cryptoWishList, dhgexpara);
0800: }
0801:
0802: /**
0803: * Returns the hostname that was passed to the constructor.
0804: *
0805: * @return the hostname
0806: */
0807: public synchronized String getHostname() {
0808: return hostname;
0809: }
0810:
0811: /**
0812: * Returns the port that was passed to the constructor.
0813: *
0814: * @return the TCP port
0815: */
0816: public synchronized int getPort() {
0817: return port;
0818: }
0819:
0820: /**
0821: * Returns a {@link ConnectionInfo} object containing the details of
0822: * the connection. Can be called as soon as the connection has been
0823: * established (successfully connected).
0824: *
0825: * @return A {@link ConnectionInfo} object.
0826: * @throws IOException
0827: * In case of any failure behind the scenes.
0828: */
0829: public synchronized ConnectionInfo getConnectionInfo()
0830: throws IOException {
0831: if (tm == null)
0832: throw new IllegalStateException(
0833: "Cannot get details of connection, you need to establish a connection first.");
0834: return tm.getConnectionInfo(1);
0835: }
0836:
0837: /**
0838: * After a successful connect, one has to authenticate oneself. This method
0839: * can be used to tell which authentication methods are supported by the
0840: * server at a certain stage of the authentication process (for the given
0841: * username).
0842: * <p>
0843: * Note 1: the username will only be used if no authentication step was done
0844: * so far (it will be used to ask the server for a list of possible
0845: * authentication methods). Otherwise, this method ignores the user name and
0846: * returns a cached method list (which is based on the information contained
0847: * in the last negative server response).
0848: * <p>
0849: * Note 2: the server may return method names that are not supported by this
0850: * implementation.
0851: * <p>
0852: * After a successful authentication, this method must not be called
0853: * anymore.
0854: *
0855: * @param user
0856: * A <code>String</code> holding the username.
0857: *
0858: * @return a (possibly emtpy) array holding authentication method names.
0859: * @throws IOException
0860: */
0861: public synchronized String[] getRemainingAuthMethods(String user)
0862: throws IOException {
0863: if (user == null)
0864: throw new IllegalArgumentException(
0865: "user argument may not be NULL!");
0866:
0867: if (tm == null)
0868: throw new IllegalStateException(
0869: "Connection is not established!");
0870:
0871: if (authenticated)
0872: throw new IllegalStateException(
0873: "Connection is already authenticated!");
0874:
0875: if (am == null)
0876: am = new AuthenticationManager(tm);
0877:
0878: if (cm == null)
0879: cm = new ChannelManager(tm);
0880:
0881: return am.getRemainingMethods(user);
0882: }
0883:
0884: /**
0885: * Determines if the authentication phase is complete. Can be called at any
0886: * time.
0887: *
0888: * @return <code>true</code> if no further authentication steps are
0889: * needed.
0890: */
0891: public synchronized boolean isAuthenticationComplete() {
0892: return authenticated;
0893: }
0894:
0895: /**
0896: * Returns true if there was at least one failed authentication request and
0897: * the last failed authentication request was marked with "partial success"
0898: * by the server. This is only needed in the rare case of SSH-2 server setups
0899: * that cannot be satisfied with a single successful authentication request
0900: * (i.e., multiple authentication steps are needed.)
0901: * <p>
0902: * If you are interested in the details, then have a look at
0903: * draft-ietf-secsh-userauth-XX.txt.
0904: *
0905: * @return if the there was a failed authentication step and the last one
0906: * was marked as a "partial success".
0907: */
0908: public synchronized boolean isAuthenticationPartialSuccess() {
0909: if (am == null)
0910: return false;
0911:
0912: return am.getPartialSuccess();
0913: }
0914:
0915: /**
0916: * Checks if a specified authentication method is available. This method is
0917: * actually just a wrapper for {@link #getRemainingAuthMethods(String)
0918: * getRemainingAuthMethods()}.
0919: *
0920: * @param user
0921: * A <code>String</code> holding the username.
0922: * @param method
0923: * An authentication method name (e.g., "publickey", "password",
0924: * "keyboard-interactive") as specified by the SSH-2 standard.
0925: * @return if the specified authentication method is currently available.
0926: * @throws IOException
0927: */
0928: public synchronized boolean isAuthMethodAvailable(String user,
0929: String method) throws IOException {
0930: if (method == null)
0931: throw new IllegalArgumentException(
0932: "method argument may not be NULL!");
0933:
0934: String methods[] = getRemainingAuthMethods(user);
0935:
0936: for (int i = 0; i < methods.length; i++) {
0937: if (methods[i].compareTo(method) == 0)
0938: return true;
0939: }
0940:
0941: return false;
0942: }
0943:
0944: private final SecureRandom getOrCreateSecureRND() {
0945: if (generator == null)
0946: generator = new SecureRandom();
0947:
0948: return generator;
0949: }
0950:
0951: /**
0952: * Open a new {@link Session} on this connection. Works only after one has passed
0953: * successfully the authentication step. There is no limit on the number of
0954: * concurrent sessions.
0955: *
0956: * @return A {@link Session} object.
0957: * @throws IOException
0958: */
0959: public synchronized Session openSession() throws IOException {
0960: if (tm == null)
0961: throw new IllegalStateException(
0962: "Cannot open session, you need to establish a connection first.");
0963:
0964: if (!authenticated)
0965: throw new IllegalStateException(
0966: "Cannot open session, connection is not authenticated.");
0967:
0968: return new Session(cm, getOrCreateSecureRND());
0969: }
0970:
0971: /**
0972: * Removes duplicates from a String array, keeps only first occurence
0973: * of each element. Does not destroy order of elements; can handle nulls.
0974: * Uses a very efficient O(N^2) algorithm =)
0975: *
0976: * @param list a String array.
0977: * @return a cleaned String array.
0978: */
0979: private String[] removeDuplicates(String[] list) {
0980: if ((list == null) || (list.length < 2))
0981: return list;
0982:
0983: String[] list2 = new String[list.length];
0984:
0985: int count = 0;
0986:
0987: for (int i = 0; i < list.length; i++) {
0988: boolean duplicate = false;
0989:
0990: String element = list[i];
0991:
0992: for (int j = 0; j < count; j++) {
0993: if (((element == null) && (list2[j] == null))
0994: || ((element != null) && (element
0995: .equals(list2[j])))) {
0996: duplicate = true;
0997: break;
0998: }
0999: }
1000:
1001: if (duplicate)
1002: continue;
1003:
1004: list2[count++] = list[i];
1005: }
1006:
1007: if (count == list2.length)
1008: return list2;
1009:
1010: String[] tmp = new String[count];
1011: System.arraycopy(list2, 0, tmp, 0, count);
1012:
1013: return tmp;
1014: }
1015:
1016: /**
1017: * Unless you know what you are doing, you will never need this.
1018: *
1019: * @param ciphers
1020: */
1021: public synchronized void setClient2ServerCiphers(String[] ciphers) {
1022: if ((ciphers == null) || (ciphers.length == 0))
1023: throw new IllegalArgumentException();
1024: ciphers = removeDuplicates(ciphers);
1025: BlockCipherFactory.checkCipherList(ciphers);
1026: cryptoWishList.c2s_enc_algos = ciphers;
1027: }
1028:
1029: /**
1030: * Unless you know what you are doing, you will never need this.
1031: *
1032: * @param macs
1033: */
1034: public synchronized void setClient2ServerMACs(String[] macs) {
1035: if ((macs == null) || (macs.length == 0))
1036: throw new IllegalArgumentException();
1037: macs = removeDuplicates(macs);
1038: MAC.checkMacList(macs);
1039: cryptoWishList.c2s_mac_algos = macs;
1040: }
1041:
1042: /**
1043: * Sets the parameters for the diffie-hellman group exchange. Unless you
1044: * know what you are doing, you will never need this. Default values are
1045: * defined in the {@link DHGexParameters} class.
1046: *
1047: * @param dgp {@link DHGexParameters}, non null.
1048: *
1049: */
1050: public synchronized void setDHGexParameters(DHGexParameters dgp) {
1051: if (dgp == null)
1052: throw new IllegalArgumentException();
1053:
1054: dhgexpara = dgp;
1055: }
1056:
1057: /**
1058: * Unless you know what you are doing, you will never need this.
1059: *
1060: * @param ciphers
1061: */
1062: public synchronized void setServer2ClientCiphers(String[] ciphers) {
1063: if ((ciphers == null) || (ciphers.length == 0))
1064: throw new IllegalArgumentException();
1065: ciphers = removeDuplicates(ciphers);
1066: BlockCipherFactory.checkCipherList(ciphers);
1067: cryptoWishList.s2c_enc_algos = ciphers;
1068: }
1069:
1070: /**
1071: * Unless you know what you are doing, you will never need this.
1072: *
1073: * @param macs
1074: */
1075: public synchronized void setServer2ClientMACs(String[] macs) {
1076: if ((macs == null) || (macs.length == 0))
1077: throw new IllegalArgumentException();
1078:
1079: macs = removeDuplicates(macs);
1080: MAC.checkMacList(macs);
1081: cryptoWishList.s2c_mac_algos = macs;
1082: }
1083:
1084: /**
1085: * Define the set of allowed server host key algorithms to be used for
1086: * the following key exchange operations.
1087: * <p>
1088: * Unless you know what you are doing, you will never need this.
1089: *
1090: * @param algos An array of allowed server host key algorithms.
1091: * SSH-2 defines <code>ssh-dss</code> and <code>ssh-rsa</code>.
1092: * The entries of the array must be ordered after preference, i.e.,
1093: * the entry at index 0 is the most preferred one. You must specify
1094: * at least one entry.
1095: */
1096: public synchronized void setServerHostKeyAlgorithms(String[] algos) {
1097: if ((algos == null) || (algos.length == 0))
1098: throw new IllegalArgumentException();
1099:
1100: algos = removeDuplicates(algos);
1101: KexManager.checkServerHostkeyAlgorithmsList(algos);
1102: cryptoWishList.serverHostKeyAlgorithms = algos;
1103: }
1104:
1105: /**
1106: * Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm) on the underlying socket.
1107: * <p>
1108: * Can be called at any time. If the connection has not yet been established
1109: * then the passed value will be stored and set after the socket has been set up.
1110: * The default value that will be used is <code>false</code>.
1111: *
1112: * @param enable the argument passed to the <code>Socket.setTCPNoDelay()</code> method.
1113: * @throws IOException
1114: */
1115: public synchronized void setTCPNoDelay(boolean enable)
1116: throws IOException {
1117: tcpNoDelay = enable;
1118:
1119: if (tm != null)
1120: tm.setTcpNoDelay(enable);
1121: }
1122:
1123: /**
1124: * Used to tell the library that the connection shall be established through a proxy server.
1125: * It only makes sense to call this method before calling the {@link #connect() connect()}
1126: * method.
1127: * <p>
1128: * At the moment, only HTTP proxies are supported.
1129: * <p>
1130: * Note: This method can be called any number of times. The {@link #connect() connect()}
1131: * method will use the value set in the last preceding invocation of this method.
1132: *
1133: * @see HTTPProxyData
1134: *
1135: * @param proxyData Connection information about the proxy. If <code>null</code>, then
1136: * no proxy will be used (non surprisingly, this is also the default).
1137: */
1138: public synchronized void setProxyData(ProxyData proxyData) {
1139: this .proxyData = proxyData;
1140: }
1141:
1142: /**
1143: * Request a remote port forwarding.
1144: * If successful, then forwarded connections will be redirected to the given target address.
1145: * You can cancle a requested remote port forwarding by calling
1146: * {@link #cancelRemotePortForwarding(int) cancelRemotePortForwarding()}.
1147: * <p>
1148: * A call of this method will block until the peer either agreed or disagreed to your request-
1149: * <p>
1150: * Note 1: this method typically fails if you
1151: * <ul>
1152: * <li>pass a port number for which the used remote user has not enough permissions (i.e., port
1153: * < 1024)</li>
1154: * <li>or pass a port number that is already in use on the remote server</li>
1155: * <li>or if remote port forwarding is disabled on the server.</li>
1156: * </ul>
1157: * <p>
1158: * Note 2: (from the openssh man page): By default, the listening socket on the server will be
1159: * bound to the loopback interface only. This may be overriden by specifying a bind address.
1160: * Specifying a remote bind address will only succeed if the server's <b>GatewayPorts</b> option
1161: * is enabled (see sshd_config(5)).
1162: *
1163: * @param bindAddress address to bind to on the server:
1164: * <ul>
1165: * <li>"" means that connections are to be accepted on all protocol families
1166: * supported by the SSH implementation</li>
1167: * <li>"0.0.0.0" means to listen on all IPv4 addresses</li>
1168: * <li>"::" means to listen on all IPv6 addresses</li>
1169: * <li>"localhost" means to listen on all protocol families supported by the SSH
1170: * implementation on loopback addresses only, [RFC3330] and RFC3513]</li>
1171: * <li>"127.0.0.1" and "::1" indicate listening on the loopback interfaces for
1172: * IPv4 and IPv6 respectively</li>
1173: * </ul>
1174: * @param bindPort port number to bind on the server (must be > 0)
1175: * @param targetAddress the target address (IP or hostname)
1176: * @param targetPort the target port
1177: * @throws IOException
1178: */
1179: public synchronized void requestRemotePortForwarding(
1180: String bindAddress, int bindPort, String targetAddress,
1181: int targetPort) throws IOException {
1182: if (tm == null)
1183: throw new IllegalStateException(
1184: "You need to establish a connection first.");
1185:
1186: if (!authenticated)
1187: throw new IllegalStateException(
1188: "The connection is not authenticated.");
1189:
1190: if ((bindAddress == null) || (targetAddress == null)
1191: || (bindPort <= 0) || (targetPort <= 0))
1192: throw new IllegalArgumentException();
1193:
1194: cm.requestGlobalForward(bindAddress, bindPort, targetAddress,
1195: targetPort);
1196: }
1197:
1198: /**
1199: * Cancel an earlier requested remote port forwarding.
1200: * Currently active forwardings will not be affected (e.g., disrupted).
1201: * Note that further connection forwarding requests may be received until
1202: * this method has returned.
1203: *
1204: * @param bindPort the allocated port number on the server
1205: * @throws IOException if the remote side refuses the cancel request or another low
1206: * level error occurs (e.g., the underlying connection is closed)
1207: */
1208: public synchronized void cancelRemotePortForwarding(int bindPort)
1209: throws IOException {
1210: if (tm == null)
1211: throw new IllegalStateException(
1212: "You need to establish a connection first.");
1213:
1214: if (!authenticated)
1215: throw new IllegalStateException(
1216: "The connection is not authenticated.");
1217:
1218: cm.requestCancelGlobalForward(bindPort);
1219: }
1220:
1221: /**
1222: * Provide your own instance of SecureRandom. Can be used, e.g., if you
1223: * want to seed the used SecureRandom generator manually.
1224: * <p>
1225: * The SecureRandom instance is used during key exchanges, public key authentication,
1226: * x11 cookie generation and the like.
1227: *
1228: * @param rnd a SecureRandom instance
1229: */
1230: public synchronized void setSecureRandom(SecureRandom rnd) {
1231: if (rnd == null)
1232: throw new IllegalArgumentException();
1233:
1234: this.generator = rnd;
1235: }
1236: }
|