/** * Start the application * * @throws IOException if there is a problem with connection * @throws InterruptedException if thread was interrupted */ private void start() throws IOException, InterruptedException { Connection c = new Connection(myHost.getHostName(), myHost.getPort()); try { configureKnownHosts(c); c.connect(new HostKeyVerifier()); authenticate(c); final Session s = c.openSession(); try { s.execCommand(myCommand); // Note that stdin is not being waited using semaphore. // Instead, the SSH process waits for remote process exit // if remote process exited, none is interested in stdin // anyway. forward("stdin", s.getStdin(), System.in, false); forward("stdout", System.out, s.getStdout(), true); forward("stderr", System.err, s.getStderr(), true); myForwardCompleted.acquire(2); // wait only for stderr and stdout s.waitForCondition(ChannelCondition.EXIT_STATUS, Long.MAX_VALUE); Integer exitStatus = s.getExitStatus(); if (exitStatus == null) { // broken exit status exitStatus = 1; } System.exit(exitStatus.intValue() == 0 ? myExitCode : exitStatus.intValue()); } finally { s.close(); } } finally { c.close(); } }
/** * Try public key * * @param c a ssh connection * @param keyPath a path to key * @return true if authentication is successful */ private boolean tryPublicKey(final Connection c, final String keyPath) { try { final File file = new File(keyPath); if (file.exists()) { // if encrypted ask user for passphrase String passphrase = null; char[] text = FileUtil.loadFileText(file); if (isEncryptedKey(text)) { // need to ask passphrase from user int i; for (i = myHost.getNumberOfPasswordPrompts(); i > 0; i--) { passphrase = myXmlRpcClient.askPassphrase( myHandlerNo, getUserHostString(), keyPath, myLastError); if (passphrase == null) { // if no passphrase was entered, just return false and try something other return false; } else { try { PEMDecoder.decode(text, passphrase); myLastError = ""; } catch (IOException e) { // decoding failed myLastError = GitBundle.message("sshmain.invalidpassphrase", keyPath); continue; } break; } } if (i == 0) { myLastError = GitBundle.message( "sshmain.too.mush.passphrase.guesses", keyPath, myHost.getNumberOfPasswordPrompts()); return false; } } // try authentication if (c.authenticateWithPublicKey(myHost.getUser(), text, passphrase)) { myLastError = ""; return true; } else { if (passphrase != null) { // mark as failed authentication only if passphrase were asked myLastError = GitBundle.message("sshmain.pk.authenitication.failed", keyPath); } else { myLastError = ""; } } } return false; } catch (Exception e) { return false; } }
/** * A constructor * * @param xmlRpcPort a xml RPC port * @param host a host * @param username a name of user (from URL) * @param port a port * @param command a command * @throws IOException if config file could not be loaded */ private SSHMain(final int xmlRpcPort, String host, String username, Integer port, String command) throws IOException { SSHConfig config = SSHConfig.load(); myHost = config.lookup(username, host, port); myXmlRpcClient = new GitSSHIdeaClient(xmlRpcPort, myHost.isBatchMode()); myHandlerNo = Integer.parseInt(System.getenv(GitSSHService.SSH_HANDLER_ENV)); myCommand = command; }
/** * Configure known host database for connection * * @param c a connection * @throws IOException if there is a IO problem */ private void configureKnownHosts(Connection c) throws IOException { File knownHostFile = new File(knownHostPath); if (knownHostFile.exists()) { database.addHostkeys(knownHostFile); } final List<String> algorithms = myHost.getHostKeyAlgorithms(); c.setServerHostKeyAlgorithms(algorithms.toArray(new String[algorithms.size()])); }
/** @return user and host string */ private String getUserHostString() { int port = myHost.getPort(); return myHost.getUser() + "@" + myHost.getHostName() + (port == 22 ? "" : ":" + port); }
/** * Authenticate using some supported methods. If authentication fails, the method throws {@link * IOException}. * * @param c the connection to use for authentication * @throws IOException in case of IO error or authentication failure */ private void authenticate(final Connection c) throws IOException { for (String method : myHost.getPreferredMethods()) { if (c.isAuthenticationComplete()) { return; } if (PUBLIC_KEY_METHOD.equals(method)) { if (!myHost.supportsPubkeyAuthentication()) { continue; } if (!c.isAuthMethodAvailable(myHost.getUser(), PUBLIC_KEY_METHOD)) { continue; } File key = myHost.getIdentityFile(); if (key == null) { for (String a : myHost.getHostKeyAlgorithms()) { if (SSH_RSA_ALGORITHM.equals(a)) { if (tryPublicKey(c, idRSAPath)) { return; } } else if (SSH_DSS_ALGORITHM.equals(a)) { if (tryPublicKey(c, idDSAPath)) { return; } } } } else { if (tryPublicKey(c, key.getPath())) { return; } } } else if (KEYBOARD_INTERACTIVE_METHOD.equals(method)) { if (!c.isAuthMethodAvailable(myHost.getUser(), KEYBOARD_INTERACTIVE_METHOD)) { continue; } InteractiveSupport interactiveSupport = new InteractiveSupport(); for (int i = myHost.getNumberOfPasswordPrompts(); i > 0; i--) { if (c.isAuthenticationComplete()) { return; } if (c.authenticateWithKeyboardInteractive(myHost.getUser(), interactiveSupport)) { myLastError = ""; return; } else { myLastError = GitBundle.getString("sshmain.keyboard.interactive.failed"); } if (interactiveSupport.myPromptCount == 0 || interactiveSupport.myCancelled) { // the interactive callback has never been asked or it was cancelled, exit the loop myLastError = ""; break; } } } else if (PASSWORD_METHOD.equals(method)) { if (!myHost.supportsPasswordAuthentication()) { continue; } if (!c.isAuthMethodAvailable(myHost.getUser(), PASSWORD_METHOD)) { continue; } for (int i = myHost.getNumberOfPasswordPrompts(); i > 0; i--) { String password = myXmlRpcClient.askPassword(myHandlerNo, getUserHostString(), myLastError); if (password == null) { break; } else { if (c.authenticateWithPassword(myHost.getUser(), password)) { myLastError = ""; return; } else { myLastError = GitBundle.getString("sshmain.password.failed"); } } } } } throw new IOException("Authentication failed"); }