/* * This file is part of LCMC written by Rasto Levrinc. * * Copyright (C) 2014, Rastislav Levrinc. * * The LCMC is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your option) * any later version. * * The LCMC is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with LCMC; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ package lcmc.cluster.service.ssh; import java.io.IOException; import lcmc.common.domain.Application; import lcmc.common.ui.utils.SwingUtils; import lcmc.host.domain.Host; import lcmc.common.ui.ProgressBar; import lcmc.cluster.ui.SSHGui; import lcmc.common.domain.ConnectionCallback; import lcmc.logger.Logger; import lcmc.logger.LoggerFactory; import lcmc.common.domain.util.Tools; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; @Named public class ConnectionThread extends Thread { private static final Logger LOG = LoggerFactory.getLogger(ConnectionThread.class); private String hostname; private SSHGui sshGui; private Host host; private ProgressBar progressBar; /** Callback when connection is failed or properly closed. */ private ConnectionCallback connectionCallback; private Authentication authentication; private volatile SshConnection sshConnection = null; private volatile boolean connectionFailed; private volatile boolean connectionEstablished = false; @Inject private Application application; @Inject private SwingUtils swingUtils; @Inject private Provider<PopupHostKeyVerifier> popupHostKeyVerifierProvider; void init(final Host host, final SSHGui sshGui, final ProgressBar progressBar, final ConnectionCallback connectionCallback, final Authentication authentication) { this.host = host; this.sshGui = sshGui; this.progressBar = progressBar; this.connectionCallback = connectionCallback; hostname = host.getFirstIp(); this.authentication = authentication; } /** Cancel the connecting. */ void cancel() { sshConnection.cancel(); } /** Start connection in the thread. */ @Override public void run() { LOG.debug2("run: start"); if (connectionCallback != null && isConnectionEstablished()) { connectionCallback.done(1); } host.setSudoPassword(""); final SshConnection newSshConnection = new SshConnection(hostname, host.getSSHPortInt()); try { if (hostname == null) { throw new IOException("hostname is not set"); } connect(newSshConnection); authenticate(newSshConnection); } catch (final IOException e) { handleFailedConnection(e.getMessage()); } } public void closeConnection() { connectionEstablished = false; } public boolean isConnectionEstablished() { return connectionEstablished; } public void closeConnectionForGood() { closeConnection(); sshConnection.disconnectForGood(); } public boolean isConnectionFailed() { return connectionFailed; } public void setConnectionFailed(final boolean connectionFailed) { this.connectionFailed = connectionFailed; if (connectionFailed) { closeConnection(); } } public SshConnection getConnection() throws IOException { if (!connectionEstablished) { throw new IOException("getConnection: connection closed"); } return sshConnection; } public boolean isDisconnectedForGood() { return sshConnection != null && sshConnection.isDisconnectedForGood(); } public void disconnectForGood() { if (sshConnection != null) { sshConnection.disconnectForGood(); } } private void connect(final SshConnection newSshConnection) throws IOException { LOG.debug2("run: verify host keys: " + hostname); final String[] hostkeyAlgos = application.getKnownHosts().getPreferredServerHostkeyAlgorithmOrder(hostname); if (hostkeyAlgos != null) { newSshConnection.setServerHostKeyAlgorithms(hostkeyAlgos); } final int connectTimeout = Tools.getDefaultInt("SSH.ConnectTimeout"); final int kexTimeout = Tools.getDefaultInt("SSH.KexTimeout"); if (progressBar != null) { final int timeout = (connectTimeout < kexTimeout) ? connectTimeout : kexTimeout; progressBar.start(timeout); } LOG.debug2("run: connect"); final PopupHostKeyVerifier popupHostKeyVerifier = popupHostKeyVerifierProvider.get(); popupHostKeyVerifier.init(sshGui); newSshConnection.connect(popupHostKeyVerifier, connectTimeout, kexTimeout); } private void handleFailedConnection(final String message) { LOG.appWarning("run: connecting failed: " + message); connectionFailed = true; if (!connectionEstablished || !sshConnection.isCanceled()) { host.getTerminalPanel().addCommandOutput(message + '\n'); host.getTerminalPanel().nextCommand(); if (connectionCallback != null) { connectionCallback.doneError(message); } } closeConnection(); } private void authenticate(final SshConnection newSshConnection) throws IOException { LOG.debug2("run: authenticate"); authentication.authenticate(newSshConnection); LOG.debug2("run: authenticate: end"); if (newSshConnection.isCanceled()) { authenticationCanceledOrTimeout(newSshConnection); } else { authenticationOk(newSshConnection); } } private void authenticationCanceledOrTimeout(final SshConnection newSshConnection) { newSshConnection.close(); LOG.debug("authenticate: closing canceled connection"); closeConnection(); host.setConnected(); if (connectionCallback != null) { connectionCallback.doneError(""); } } private void authenticationOk(final SshConnection newSshConnection) { sshConnection = newSshConnection; connectionEstablished = true; host.setConnected(); swingUtils.invokeLater(new Runnable() { @Override public void run() { host.getTerminalPanel().nextCommand(); } }); final Thread thread = new Thread(new Runnable() { @Override public void run() { if (connectionCallback != null) { connectionCallback.done(0); } } }); thread.start(); LOG.debug1("authenticate: " + host.getName() + ": authentication ok"); } }