diff --git a/plugins/org.jkiss.dbeaver.core/src/org/jkiss/dbeaver/core/CoreMessages.java b/plugins/org.jkiss.dbeaver.core/src/org/jkiss/dbeaver/core/CoreMessages.java index f7f345daa99c8403f5caf211cd9db029c83d2c8d..05856ec491fba2e2ef45cf73716fd209aa5157b2 100644 --- a/plugins/org.jkiss.dbeaver.core/src/org/jkiss/dbeaver/core/CoreMessages.java +++ b/plugins/org.jkiss.dbeaver.core/src/org/jkiss/dbeaver/core/CoreMessages.java @@ -758,6 +758,7 @@ public class CoreMessages extends NLS { public static String model_ssh_configurator_label_port; public static String model_ssh_configurator_label_private_key; public static String model_ssh_configurator_label_user_name; + public static String model_ssh_configurator_label_implementation; public static String model_ssh_configurator_label_local_port; public static String model_ssh_configurator_label_local_port_description; public static String model_ssh_configurator_label_keep_alive; diff --git a/plugins/org.jkiss.dbeaver.core/src/org/jkiss/dbeaver/core/CoreResources.properties b/plugins/org.jkiss.dbeaver.core/src/org/jkiss/dbeaver/core/CoreResources.properties index 6ac0bfd9ab72cace03bc9ef4b168984e330fcb85..74ae79a0954c36b9d976ae83e45819a54ba2f462 100644 --- a/plugins/org.jkiss.dbeaver.core/src/org/jkiss/dbeaver/core/CoreResources.properties +++ b/plugins/org.jkiss.dbeaver.core/src/org/jkiss/dbeaver/core/CoreResources.properties @@ -752,6 +752,7 @@ model_ssh_configurator_label_password = Password model_ssh_configurator_label_port = Port model_ssh_configurator_label_private_key = Private Key model_ssh_configurator_label_user_name = User Name +model_ssh_configurator_label_implementation = Implementation model_ssh_configurator_label_local_port = Local port model_ssh_configurator_label_local_port_description = Local port for tunnel. If set to <=0 then random free port (>10000) will be acquired model_ssh_configurator_label_keep_alive = Keep-Alive interval (ms) diff --git a/plugins/org.jkiss.dbeaver.core/src/org/jkiss/dbeaver/ui/dialogs/net/SSHTunnelConfiguratorUI.java b/plugins/org.jkiss.dbeaver.core/src/org/jkiss/dbeaver/ui/dialogs/net/SSHTunnelConfiguratorUI.java index e9290c9352ce8f2000489b397b5938f34ba1c401..e4c68b500a538a216047e354401184065e6185f7 100644 --- a/plugins/org.jkiss.dbeaver.core/src/org/jkiss/dbeaver/ui/dialogs/net/SSHTunnelConfiguratorUI.java +++ b/plugins/org.jkiss.dbeaver.core/src/org/jkiss/dbeaver/ui/dialogs/net/SSHTunnelConfiguratorUI.java @@ -25,6 +25,7 @@ import org.eclipse.swt.widgets.*; import org.jkiss.dbeaver.core.CoreMessages; import org.jkiss.dbeaver.core.DBeaverUI; import org.jkiss.dbeaver.model.impl.net.SSHConstants; +import org.jkiss.dbeaver.model.impl.net.SSHImplType; import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration; import org.jkiss.dbeaver.ui.IObjectPropertyConfigurator; import org.jkiss.dbeaver.ui.UIUtils; @@ -48,6 +49,7 @@ public class SSHTunnelConfiguratorUI implements IObjectPropertyConfigurator hostText.getParent().getParent().layout(true, true)); } @Override diff --git a/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/impl/net/SSHImplType.java b/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/impl/net/SSHImplType.java new file mode 100644 index 0000000000000000000000000000000000000000..108e90c060c4082022408f993482221b9f9d00f3 --- /dev/null +++ b/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/impl/net/SSHImplType.java @@ -0,0 +1,57 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jkiss.dbeaver.model.impl.net; + +/** + * SSH implementation enum + */ +public enum SSHImplType { + + JSCH("jsch", "JSch", SSHImplementationJsch.class), + SSHJ("sshj", "SSHJ", SSHImplementationSshj.class); + + private String id; + private String label; + private Class implClass; + + SSHImplType(String id, String label, Class implClass) { + this.id = id; + this.label = label; + this.implClass = implClass; + } + + public String getId() { + return id; + } + + public String getLabel() { + return label; + } + + public Class getImplClass() { + return implClass; + } + + public static SSHImplType getById(String id) throws IllegalArgumentException { + for (SSHImplType it : values()) { + if (it.getId().equals(id)) { + return it; + } + } + throw new IllegalArgumentException("Bad SSH impl: " + id); + } +} \ No newline at end of file diff --git a/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/impl/net/SSHImplementation.java b/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/impl/net/SSHImplementation.java index 1696e7dbd9bb0df918cef8cfba0720b434ffe670..f18954d72f71e1011dfaa3dc930757e404c5a45d 100644 --- a/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/impl/net/SSHImplementation.java +++ b/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/impl/net/SSHImplementation.java @@ -29,10 +29,13 @@ import java.io.IOException; */ public interface SSHImplementation { - DBPConnectionConfiguration startPortForward(DBRProgressMonitor monitor, DBPPlatform platform, DBWHandlerConfiguration configuration, DBPConnectionConfiguration connectionInfo) + DBPConnectionConfiguration initTunnel(DBRProgressMonitor monitor, DBPPlatform platform, DBWHandlerConfiguration configuration, DBPConnectionConfiguration connectionInfo) throws DBException, IOException; - void stopPortForward(DBRProgressMonitor monitor) + void invalidateTunnel(DBRProgressMonitor monitor) + throws DBException, IOException; + + void closeTunnel(DBRProgressMonitor monitor) throws DBException, IOException; } diff --git a/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/impl/net/SSHImplementationAbstract.java b/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/impl/net/SSHImplementationAbstract.java new file mode 100644 index 0000000000000000000000000000000000000000..6fdaeb4e54a4d4e946ddd67bff4978eae3aaa1db --- /dev/null +++ b/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/impl/net/SSHImplementationAbstract.java @@ -0,0 +1,145 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jkiss.dbeaver.model.impl.net; + +import org.jkiss.dbeaver.DBException; +import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.model.app.DBPPlatform; +import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration; +import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration; +import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; +import org.jkiss.utils.CommonUtils; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +/** + * SSH tunnel + */ +public abstract class SSHImplementationAbstract implements SSHImplementation { + + private static final Log log = Log.getLog(SSHImplementationAbstract.class); + + // Saved config - used for tunnel invalidate + private transient int savedLocalPort; + protected transient DBWHandlerConfiguration savedConfiguration; + protected transient DBPConnectionConfiguration savedConnectionInfo; + + @Override + public DBPConnectionConfiguration initTunnel(DBRProgressMonitor monitor, DBPPlatform platform, DBWHandlerConfiguration configuration, DBPConnectionConfiguration connectionInfo) + throws DBException, IOException + { + String dbPortString = connectionInfo.getHostPort(); + if (CommonUtils.isEmpty(dbPortString)) { + dbPortString = configuration.getDriver().getDefaultPort(); + if (CommonUtils.isEmpty(dbPortString)) { + throw new DBException("Database port not specified and no default port number for driver '" + configuration.getDriver().getName() + "'"); + } + } + String dbHost = connectionInfo.getHostName(); + + Map properties = configuration.getProperties(); + String sshAuthType = properties.get(SSHConstants.PROP_AUTH_TYPE); + String sshHost = properties.get(SSHConstants.PROP_HOST); + String sshPort = properties.get(SSHConstants.PROP_PORT); + String sshLocalPort = properties.get(SSHConstants.PROP_LOCAL_PORT); + String sshUser = configuration.getUserName(); + String aliveInterval = properties.get(SSHConstants.PROP_ALIVE_INTERVAL); + String connectTimeoutString = properties.get(SSHConstants.PROP_CONNECT_TIMEOUT); + //String aliveCount = properties.get(SSHConstants.PROP_ALIVE_COUNT); + if (CommonUtils.isEmpty(sshHost)) { + throw new DBException("SSH host not specified"); + } + if (CommonUtils.isEmpty(sshPort)) { + throw new DBException("SSH port not specified"); + } + if (CommonUtils.isEmpty(sshUser)) { + throw new DBException("SSH user not specified"); + } + int sshPortNum; + try { + sshPortNum = Integer.parseInt(sshPort); + } + catch (NumberFormatException e) { + throw new DBException("Invalid SSH port: " + sshPort); + } + SSHConstants.AuthType authType = SSHConstants.AuthType.PASSWORD; + if (sshAuthType != null) { + authType = SSHConstants.AuthType.valueOf(sshAuthType); + } + File privKeyFile = null; + String privKeyPath = properties.get(SSHConstants.PROP_KEY_PATH); + if (authType == SSHConstants.AuthType.PUBLIC_KEY) { + if (CommonUtils.isEmpty(privKeyPath)) { + throw new DBException("Private key path is empty"); + } + privKeyFile = new File(privKeyPath); + if (!privKeyFile.exists()) { + throw new DBException("Private key file '" + privKeyFile.getAbsolutePath() + "' doesn't exist"); + } + } + int connectTimeout; + try { + connectTimeout = Integer.parseInt(connectTimeoutString); + } + catch (NumberFormatException e) { + connectTimeout = SSHConstants.DEFAULT_CONNECT_TIMEOUT; + } + + monitor.subTask("Initiating tunnel at '" + sshHost + "'"); + int dbPort; + try { + dbPort = Integer.parseInt(dbPortString); + } catch (NumberFormatException e) { + throw new DBException("Bad database port number: " + dbPortString); + } + int localPort = savedLocalPort; + if (platform != null) { + localPort = SSHUtils.findFreePort(platform); + } + if (!CommonUtils.isEmpty(sshLocalPort)) { + try { + int forceLocalPort = Integer.parseInt(sshLocalPort); + if (forceLocalPort > 0) { + localPort = forceLocalPort; + } + } catch (NumberFormatException e) { + log.warn("Bad local port specified", e); + } + } + + setupTunnel(monitor, configuration, dbHost, sshHost, sshUser, aliveInterval, sshPortNum, privKeyFile, connectTimeout, dbPort, localPort); + savedLocalPort = localPort; + savedConfiguration = configuration; + savedConnectionInfo = connectionInfo; + + connectionInfo = new DBPConnectionConfiguration(connectionInfo); + String newPortValue = String.valueOf(localPort); + // Replace database host/port and URL - let's use localhost + connectionInfo.setHostName(SSHConstants.LOCALHOST_NAME); + connectionInfo.setHostPort(newPortValue); + String newURL = configuration.getDriver().getDataSourceProvider().getConnectionURL( + configuration.getDriver(), + connectionInfo); + connectionInfo.setUrl(newURL); + return connectionInfo; + } + + protected abstract void setupTunnel(DBRProgressMonitor monitor, DBWHandlerConfiguration configuration, String dbHost, String sshHost, String sshUser, String aliveInterval, int sshPortNum, File privKeyFile, int connectTimeout, int dbPort, int localPort) throws DBException, IOException; + +} diff --git a/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/impl/net/SSHImplementationJsch.java b/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/impl/net/SSHImplementationJsch.java new file mode 100644 index 0000000000000000000000000000000000000000..ee0e1cc79051c7fe00d736fd661d6456bb0a9c34 --- /dev/null +++ b/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/impl/net/SSHImplementationJsch.java @@ -0,0 +1,178 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jkiss.dbeaver.model.impl.net; + +import com.jcraft.jsch.*; +import org.jkiss.dbeaver.DBException; +import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration; +import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; +import org.jkiss.dbeaver.utils.RuntimeUtils; +import org.jkiss.utils.CommonUtils; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; + +/** + * SSH tunnel + */ +public class SSHImplementationJsch extends SSHImplementationAbstract { + + private static final Log log = Log.getLog(SSHImplementationJsch.class); + + private static transient JSch jsch; + private transient Session session; + + @Override + protected void setupTunnel(DBRProgressMonitor monitor, DBWHandlerConfiguration configuration, String dbHost, String sshHost, String sshUser, String aliveInterval, int sshPortNum, File privKeyFile, int connectTimeout, int dbPort, int localPort) throws DBException, IOException { + UserInfo ui = new UIUserInfo(configuration); + try { + if (jsch == null) { + jsch = new JSch(); + JSch.setLogger(new LoggerProxy()); + } + if (privKeyFile != null) { + if (!CommonUtils.isEmpty(ui.getPassphrase())) { + jsch.addIdentity(privKeyFile.getAbsolutePath(), ui.getPassphrase()); + } else { + jsch.addIdentity(privKeyFile.getAbsolutePath()); + } + } + + log.debug("Instantiate SSH tunnel"); + session = jsch.getSession(sshUser, sshHost, sshPortNum); + session.setConfig("StrictHostKeyChecking", "no"); + //session.setConfig("PreferredAuthentications", "password,publickey,keyboard-interactive"); + session.setConfig("PreferredAuthentications", + privKeyFile != null ? "publickey" : "password"); + session.setConfig("ConnectTimeout", String.valueOf(connectTimeout)); + session.setUserInfo(ui); + if (!CommonUtils.isEmpty(aliveInterval)) { + session.setServerAliveInterval(Integer.parseInt(aliveInterval)); + } + log.debug("Connect to tunnel host"); + session.connect(connectTimeout); + try { + session.setPortForwardingL(localPort, dbHost, dbPort); + } catch (JSchException e) { + closeTunnel(monitor); + throw e; + } + } catch (JSchException e) { + throw new DBException("Cannot establish tunnel", e); + } + } + + @Override + public void closeTunnel(DBRProgressMonitor monitor) throws DBException, IOException { + if (session != null) { + RuntimeUtils.runTask(monitor1 -> { + try { + session.disconnect(); + } catch (Exception e) { + throw new InvocationTargetException(e); + } + }, "Close SSH session", 1000); + session = null; + } + } + + @Override + public void invalidateTunnel(DBRProgressMonitor monitor) throws DBException, IOException { + boolean isAlive = session != null && session.isConnected(); + if (isAlive) { + try { + session.sendKeepAliveMsg(); + } catch (Exception e) { + isAlive = false; + } + } + if (!isAlive) { + closeTunnel(monitor); + initTunnel(monitor, null, savedConfiguration, savedConnectionInfo); + } + } + + private class UIUserInfo implements UserInfo { + DBWHandlerConfiguration configuration; + private UIUserInfo(DBWHandlerConfiguration configuration) + { + this.configuration = configuration; + } + + @Override + public String getPassphrase() + { + return configuration.getPassword(); + } + + @Override + public String getPassword() + { + return configuration.getPassword(); + } + + @Override + public boolean promptPassword(String message) + { + return true; + } + + @Override + public boolean promptPassphrase(String message) + { + return true; + } + + @Override + public boolean promptYesNo(String message) + { + return false; + } + + @Override + public void showMessage(String message) + { + log.info(message); + } + } + + private class LoggerProxy implements Logger { + @Override + public boolean isEnabled(int level) { + return true; + } + + @Override + public void log(int level, String message) { + String levelStr; + switch (level) { + case INFO: levelStr = "INFO"; break; + case WARN: levelStr = "WARN"; break; + case ERROR: levelStr = "ERROR"; break; + case FATAL: levelStr = "FATAL"; break; + case DEBUG: + default: + levelStr = "DEBUG"; + break; + } + log.debug("SSH " + levelStr + ": " + message); + + } + } +} diff --git a/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/impl/net/SSHImplementationSshj.java b/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/impl/net/SSHImplementationSshj.java new file mode 100644 index 0000000000000000000000000000000000000000..2e4f85acd40a3ae11ff7bf1cb33e2ae8985bece6 --- /dev/null +++ b/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/impl/net/SSHImplementationSshj.java @@ -0,0 +1,178 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jkiss.dbeaver.model.impl.net; + +import com.jcraft.jsch.*; +import org.jkiss.dbeaver.DBException; +import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration; +import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; +import org.jkiss.dbeaver.utils.RuntimeUtils; +import org.jkiss.utils.CommonUtils; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; + +/** + * SSHJ tunnel + */ +public class SSHImplementationSshj extends SSHImplementationAbstract { + + private static final Log log = Log.getLog(SSHImplementationSshj.class); + + private static transient JSch jsch; + private transient Session session; + + @Override + protected void setupTunnel(DBRProgressMonitor monitor, DBWHandlerConfiguration configuration, String dbHost, String sshHost, String sshUser, String aliveInterval, int sshPortNum, File privKeyFile, int connectTimeout, int dbPort, int localPort) throws DBException, IOException { + UserInfo ui = new UIUserInfo(configuration); + try { + if (jsch == null) { + jsch = new JSch(); + JSch.setLogger(new LoggerProxy()); + } + if (privKeyFile != null) { + if (!CommonUtils.isEmpty(ui.getPassphrase())) { + jsch.addIdentity(privKeyFile.getAbsolutePath(), ui.getPassphrase()); + } else { + jsch.addIdentity(privKeyFile.getAbsolutePath()); + } + } + + log.debug("Instantiate SSH tunnel"); + session = jsch.getSession(sshUser, sshHost, sshPortNum); + session.setConfig("StrictHostKeyChecking", "no"); + //session.setConfig("PreferredAuthentications", "password,publickey,keyboard-interactive"); + session.setConfig("PreferredAuthentications", + privKeyFile != null ? "publickey" : "password"); + session.setConfig("ConnectTimeout", String.valueOf(connectTimeout)); + session.setUserInfo(ui); + if (!CommonUtils.isEmpty(aliveInterval)) { + session.setServerAliveInterval(Integer.parseInt(aliveInterval)); + } + log.debug("Connect to tunnel host"); + session.connect(connectTimeout); + try { + session.setPortForwardingL(localPort, dbHost, dbPort); + } catch (JSchException e) { + closeTunnel(monitor); + throw e; + } + } catch (JSchException e) { + throw new DBException("Cannot establish tunnel", e); + } + } + + @Override + public void closeTunnel(DBRProgressMonitor monitor) throws DBException, IOException { + if (session != null) { + RuntimeUtils.runTask(monitor1 -> { + try { + session.disconnect(); + } catch (Exception e) { + throw new InvocationTargetException(e); + } + }, "Close SSH session", 1000); + session = null; + } + } + + @Override + public void invalidateTunnel(DBRProgressMonitor monitor) throws DBException, IOException { + boolean isAlive = session != null && session.isConnected(); + if (isAlive) { + try { + session.sendKeepAliveMsg(); + } catch (Exception e) { + isAlive = false; + } + } + if (!isAlive) { + closeTunnel(monitor); + initTunnel(monitor, null, savedConfiguration, savedConnectionInfo); + } + } + + private class UIUserInfo implements UserInfo { + DBWHandlerConfiguration configuration; + private UIUserInfo(DBWHandlerConfiguration configuration) + { + this.configuration = configuration; + } + + @Override + public String getPassphrase() + { + return configuration.getPassword(); + } + + @Override + public String getPassword() + { + return configuration.getPassword(); + } + + @Override + public boolean promptPassword(String message) + { + return true; + } + + @Override + public boolean promptPassphrase(String message) + { + return true; + } + + @Override + public boolean promptYesNo(String message) + { + return false; + } + + @Override + public void showMessage(String message) + { + log.info(message); + } + } + + private class LoggerProxy implements Logger { + @Override + public boolean isEnabled(int level) { + return true; + } + + @Override + public void log(int level, String message) { + String levelStr; + switch (level) { + case INFO: levelStr = "INFO"; break; + case WARN: levelStr = "WARN"; break; + case ERROR: levelStr = "ERROR"; break; + case FATAL: levelStr = "FATAL"; break; + case DEBUG: + default: + levelStr = "DEBUG"; + break; + } + log.debug("SSH " + levelStr + ": " + message); + + } + } +} diff --git a/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/impl/net/SSHTunnelImpl.java b/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/impl/net/SSHTunnelImpl.java index 30427d383b97335c16a8db8ce0b4144048c63db1..620eb9bfcac972dfc68fefc1644cd542d1dc2146 100644 --- a/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/impl/net/SSHTunnelImpl.java +++ b/plugins/org.jkiss.dbeaver.model/src/org/jkiss/dbeaver/model/impl/net/SSHTunnelImpl.java @@ -16,7 +16,6 @@ */ package org.jkiss.dbeaver.model.impl.net; -import com.jcraft.jsch.*; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.app.DBPPlatform; @@ -24,12 +23,9 @@ import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration; import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration; import org.jkiss.dbeaver.model.net.DBWTunnel; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; -import org.jkiss.dbeaver.utils.RuntimeUtils; import org.jkiss.utils.CommonUtils; -import java.io.File; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; import java.util.Map; /** @@ -39,160 +35,36 @@ public class SSHTunnelImpl implements DBWTunnel { private static final Log log = Log.getLog(SSHTunnelImpl.class); - private static transient JSch jsch; - private transient Session session; - - // Saved config - used for tunnel invalidate - private transient int savedLocalPort; - private transient DBWHandlerConfiguration savedConfiguration; - private transient DBPConnectionConfiguration savedConnectionInfo; + private SSHImplementation implementation; @Override public DBPConnectionConfiguration initializeTunnel(DBRProgressMonitor monitor, DBPPlatform platform, DBWHandlerConfiguration configuration, DBPConnectionConfiguration connectionInfo) throws DBException, IOException { - String dbPortString = connectionInfo.getHostPort(); - if (CommonUtils.isEmpty(dbPortString)) { - dbPortString = configuration.getDriver().getDefaultPort(); - if (CommonUtils.isEmpty(dbPortString)) { - throw new DBException("Database port not specified and no default port number for driver '" + configuration.getDriver().getName() + "'"); - } - } - String dbHost = connectionInfo.getHostName(); - Map properties = configuration.getProperties(); - String sshAuthType = properties.get(SSHConstants.PROP_AUTH_TYPE); - String sshHost = properties.get(SSHConstants.PROP_HOST); - String sshPort = properties.get(SSHConstants.PROP_PORT); - String sshLocalPort = properties.get(SSHConstants.PROP_LOCAL_PORT); - String sshUser = configuration.getUserName(); - String aliveInterval = properties.get(SSHConstants.PROP_ALIVE_INTERVAL); - String connectTimeoutString = properties.get(SSHConstants.PROP_CONNECT_TIMEOUT); - //String aliveCount = properties.get(SSHConstants.PROP_ALIVE_COUNT); - if (CommonUtils.isEmpty(sshHost)) { - throw new DBException("SSH host not specified"); - } - if (CommonUtils.isEmpty(sshPort)) { - throw new DBException("SSH port not specified"); - } - if (CommonUtils.isEmpty(sshUser)) { - throw new DBException("SSH user not specified"); - } - int sshPortNum; - try { - sshPortNum = Integer.parseInt(sshPort); - } - catch (NumberFormatException e) { - throw new DBException("Invalid SSH port: " + sshPort); - } - SSHConstants.AuthType authType = SSHConstants.AuthType.PASSWORD; - if (sshAuthType != null) { - authType = SSHConstants.AuthType.valueOf(sshAuthType); - } - File privKeyFile = null; - String privKeyPath = properties.get(SSHConstants.PROP_KEY_PATH); - if (authType == SSHConstants.AuthType.PUBLIC_KEY) { - if (CommonUtils.isEmpty(privKeyPath)) { - throw new DBException("Private key path is empty"); - } - privKeyFile = new File(privKeyPath); - if (!privKeyFile.exists()) { - throw new DBException("Private key file '" + privKeyFile.getAbsolutePath() + "' doesn't exist"); - } - } - int connectTimeout; - try { - connectTimeout = Integer.parseInt(connectTimeoutString); - } - catch (NumberFormatException e) { - connectTimeout = SSHConstants.DEFAULT_CONNECT_TIMEOUT; - } - - monitor.subTask("Initiating tunnel at '" + sshHost + "'"); - UserInfo ui = new UIUserInfo(configuration); - int dbPort; - try { - dbPort = Integer.parseInt(dbPortString); - } catch (NumberFormatException e) { - throw new DBException("Bad database port number: " + dbPortString); - } - int localPort = savedLocalPort; - if (platform != null) { - localPort = SSHUtils.findFreePort(platform); - } - if (!CommonUtils.isEmpty(sshLocalPort)) { + String implId = properties.get(SSHConstants.PROP_IMPLEMENTATION); + SSHImplType implType = SSHImplType.JSCH; + if (!CommonUtils.isEmpty(implId)) { try { - int forceLocalPort = Integer.parseInt(sshLocalPort); - if (forceLocalPort > 0) { - localPort = forceLocalPort; - } - } catch (NumberFormatException e) { - log.warn("Bad local port specified", e); + implType = SSHImplType.getById(implId); + } catch (IllegalArgumentException e) { + log.error(e); } } try { - if (jsch == null) { - jsch = new JSch(); - JSch.setLogger(new LoggerProxy()); - } - if (privKeyFile != null) { - if (!CommonUtils.isEmpty(ui.getPassphrase())) { - jsch.addIdentity(privKeyFile.getAbsolutePath(), ui.getPassphrase()); - } else { - jsch.addIdentity(privKeyFile.getAbsolutePath()); - } - } - - log.debug("Instantiate SSH tunnel"); - session = jsch.getSession(sshUser, sshHost, sshPortNum); - session.setConfig("StrictHostKeyChecking", "no"); - //session.setConfig("PreferredAuthentications", "password,publickey,keyboard-interactive"); - session.setConfig("PreferredAuthentications", - privKeyFile != null ? "publickey" : "password"); - session.setConfig("ConnectTimeout", String.valueOf(connectTimeout)); - session.setUserInfo(ui); - if (!CommonUtils.isEmpty(aliveInterval)) { - session.setServerAliveInterval(Integer.parseInt(aliveInterval)); - } - log.debug("Connect to tunnel host"); - session.connect(connectTimeout); - try { - session.setPortForwardingL(localPort, dbHost, dbPort); - } catch (JSchException e) { - closeTunnel(monitor); - throw e; - } - } catch (JSchException e) { - throw new DBException("Cannot establish tunnel", e); + implementation = implType.getImplClass().newInstance(); + } catch (Throwable e) { + throw new DBException("Can't create SSH tunnel implementation", e); } - savedLocalPort = localPort; - savedConfiguration = configuration; - savedConnectionInfo = connectionInfo; - - connectionInfo = new DBPConnectionConfiguration(connectionInfo); - String newPortValue = String.valueOf(localPort); - // Replace database host/port and URL - let's use localhost - connectionInfo.setHostName(SSHConstants.LOCALHOST_NAME); - connectionInfo.setHostPort(newPortValue); - String newURL = configuration.getDriver().getDataSourceProvider().getConnectionURL( - configuration.getDriver(), - connectionInfo); - connectionInfo.setUrl(newURL); - return connectionInfo; + return implementation.initTunnel(monitor, platform, configuration, connectionInfo); } @Override public void closeTunnel(DBRProgressMonitor monitor) throws DBException, IOException { - if (session != null) { - RuntimeUtils.runTask(monitor1 -> { - try { - session.disconnect(); - } catch (Exception e) { - throw new InvocationTargetException(e); - } - }, "Close SSH session", 1000); - session = null; + if (implementation != null) { + implementation.closeTunnel(monitor); + implementation = null; } } @@ -224,85 +96,9 @@ public class SSHTunnelImpl implements DBWTunnel { @Override public void invalidateHandler(DBRProgressMonitor monitor) throws DBException, IOException { - boolean isAlive = session != null && session.isConnected(); - if (isAlive) { - try { - session.sendKeepAliveMsg(); - } catch (Exception e) { - isAlive = false; - } - } - if (!isAlive) { - closeTunnel(monitor); - initializeTunnel(monitor, null, savedConfiguration, savedConnectionInfo); + if (implementation != null) { + implementation.invalidateTunnel(monitor); } } - private class UIUserInfo implements UserInfo { - DBWHandlerConfiguration configuration; - private UIUserInfo(DBWHandlerConfiguration configuration) - { - this.configuration = configuration; - } - - @Override - public String getPassphrase() - { - return configuration.getPassword(); - } - - @Override - public String getPassword() - { - return configuration.getPassword(); - } - - @Override - public boolean promptPassword(String message) - { - return true; - } - - @Override - public boolean promptPassphrase(String message) - { - return true; - } - - @Override - public boolean promptYesNo(String message) - { - return false; - } - - @Override - public void showMessage(String message) - { - log.info(message); - } - } - - private class LoggerProxy implements Logger { - @Override - public boolean isEnabled(int level) { - return true; - } - - @Override - public void log(int level, String message) { - String levelStr; - switch (level) { - case INFO: levelStr = "INFO"; break; - case WARN: levelStr = "WARN"; break; - case ERROR: levelStr = "ERROR"; break; - case FATAL: levelStr = "FATAL"; break; - case DEBUG: - default: - levelStr = "DEBUG"; - break; - } - log.debug("SSH " + levelStr + ": " + message); - - } - } }