public CConn(VncViewer viewer_, Socket sock_,
String vncServerName)
{
- serverHost = null; serverPort = 0; sock = sock_; viewer = viewer_;
+ sock = sock_; viewer = viewer_;
pendingPFChange = false;
currentEncoding = Encodings.encodingTight; lastServerEncoding = -1;
fullColour = viewer.fullColour.getValue();
} else {
if (vncServerName != null &&
!viewer.alwaysShowServerDialog.getValue()) {
- serverHost = Hostname.getHost(vncServerName);
- serverPort = Hostname.getPort(vncServerName);
+ setServerName(Hostname.getHost(vncServerName));
+ setServerPort(Hostname.getPort(vncServerName));
} else {
ServerDialog dlg = new ServerDialog(options, vncServerName, this);
boolean ret = dlg.showDialog();
close();
return;
}
- serverHost = viewer.vncServerName.getValueStr();
- serverPort = viewer.vncServerPort.getValue();
+ setServerName(viewer.vncServerName.getValueStr());
+ setServerPort(viewer.vncServerPort.getValue());
}
try {
- sock = new TcpSocket(serverHost, serverPort);
+ if (viewer.tunnel.getValue() || (viewer.via.getValue() != null)) {
+ int localPort = TcpSocket.findFreeTcpPort();
+ if (localPort == 0)
+ throw new Exception("Could not obtain free TCP port");
+ Tunnel.createTunnel(this, localPort);
+ sock = new TcpSocket("localhost", localPort);
+ } else {
+ sock = new TcpSocket(getServerName(), getServerPort());
+ }
} catch (java.lang.Exception e) {
throw new Exception(e.getMessage());
}
- vlog.info("connected to host "+serverHost+" port "+serverPort);
+ vlog.info("connected to host "+getServerName()+" port "+getServerPort());
}
sock.inStream().setBlockCallback(this);
- setServerName(serverHost);
setStreams(sock.inStream(), sock.outStream());
initialiseProtocol();
}
options.sendLocalUsername.setEnabled(false);
options.cfLoadButton.setEnabled(false);
options.cfSaveAsButton.setEnabled(true);
+ options.sshTunnel.setEnabled(false);
+ options.sshUseGateway.setEnabled(false);
+ options.sshUser.setEnabled(false);
+ options.sshHost.setEnabled(false);
+ options.sshPort.setEnabled(false);
+ options.sshUseExt.setEnabled(false);
+ options.sshClient.setEnabled(false);
+ options.sshClientBrowser.setEnabled(false);
+ options.sshArgsDefault.setEnabled(false);
+ options.sshArgsCustom.setEnabled(false);
+ options.sshArguments.setEnabled(false);
+ options.sshConfig.setEnabled(false);
+ options.sshConfigBrowser.setEnabled(false);
+ options.sshKeyFile.setEnabled(false);
+ options.sshKeyFileBrowser.setEnabled(false);
} else {
options.shared.setSelected(viewer.shared.getValue());
options.sendLocalUsername.setSelected(viewer.sendLocalUsername.getValue());
options.cfSaveAsButton.setEnabled(false);
+ if (viewer.tunnel.getValue() || viewer.via.getValue() != null)
+ options.sshTunnel.setSelected(true);
+ if (viewer.via.getValue() != null)
+ options.sshUseGateway.setSelected(true);
+ options.sshUser.setText(Tunnel.getSshUser(this));
+ options.sshHost.setText(Tunnel.getSshHost(this));
+ options.sshPort.setText(Integer.toString(Tunnel.getSshPort(this)));
+ options.sshUseExt.setSelected(viewer.extSSH.getValue());
+ File client = new File(viewer.extSSHClient.getValue());
+ if (client.exists() && client.canRead())
+ options.sshClient.setText(client.getAbsolutePath());
+ if (viewer.extSSHArgs.getValue() == null) {
+ options.sshArgsDefault.setSelected(true);
+ options.sshArguments.setText("");
+ } else {
+ options.sshArgsCustom.setSelected(true);
+ options.sshArguments.setText(viewer.extSSHArgs.getValue());
+ }
+ File config = new File(viewer.sshConfig.getValue());
+ if (config.exists() && config.canRead())
+ options.sshConfig.setText(config.getAbsolutePath());
+ options.sshKeyFile.setText(Tunnel.getSshKeyFile(this));
/* Process non-VeNCrypt sectypes */
java.util.List<Integer> secTypes = new ArrayList<Integer>();
options.secPlain.setEnabled(options.secVeNCrypt.isSelected());
options.sendLocalUsername.setEnabled(options.secPlain.isSelected()||
options.secIdent.isSelected());
+ options.sshTunnel.setEnabled(true);
+ options.sshUseGateway.setEnabled(options.sshTunnel.isSelected());
+ options.sshUser.setEnabled(options.sshTunnel.isSelected() &&
+ options.sshUseGateway.isEnabled() &&
+ options.sshUseGateway.isSelected());
+ options.sshHost.setEnabled(options.sshTunnel.isSelected() &&
+ options.sshUseGateway.isEnabled() &&
+ options.sshUseGateway.isSelected());
+ options.sshPort.setEnabled(options.sshTunnel.isSelected() &&
+ options.sshUseGateway.isEnabled() &&
+ options.sshUseGateway.isSelected());
+ options.sshUseExt.setEnabled(options.sshTunnel.isSelected());
+ options.sshClient.setEnabled(options.sshTunnel.isSelected() &&
+ options.sshUseExt.isEnabled() &&
+ options.sshUseExt.isSelected());
+ options.sshClientBrowser.setEnabled(options.sshTunnel.isSelected() &&
+ options.sshUseExt.isEnabled() &&
+ options.sshUseExt.isSelected());
+ options.sshArgsDefault.setEnabled(options.sshTunnel.isSelected() &&
+ options.sshUseExt.isEnabled() &&
+ options.sshUseExt.isSelected());
+ options.sshArgsCustom.setEnabled(options.sshTunnel.isSelected() &&
+ options.sshUseExt.isEnabled() &&
+ options.sshUseExt.isSelected());
+ options.sshArguments.setEnabled(options.sshTunnel.isSelected() &&
+ options.sshUseExt.isEnabled() &&
+ options.sshUseExt.isSelected() &&
+ options.sshArgsCustom.isSelected());
+ options.sshConfig.setEnabled(options.sshTunnel.isSelected() &&
+ options.sshUseExt.isEnabled() &&
+ !options.sshUseExt.isSelected());
+ options.sshConfigBrowser.setEnabled(options.sshTunnel.isSelected() &&
+ options.sshUseExt.isEnabled() &&
+ !options.sshUseExt.isSelected());
+ options.sshKeyFile.setEnabled(options.sshTunnel.isSelected() &&
+ options.sshUseExt.isEnabled() &&
+ !options.sshUseExt.isSelected());
+ options.sshKeyFileBrowser.setEnabled(options.sshTunnel.isSelected() &&
+ options.sshUseExt.isEnabled() &&
+ !options.sshUseExt.isSelected());
}
options.fullScreen.setSelected(fullScreen);
if (desktop != null)
desktop.resetLocalCursor();
}
+ viewer.extSSH.setParam(options.sshUseExt.isSelected());
checkEncodings();
Security.DisableSecType(Security.secTypeTLSIdent);
Security.DisableSecType(Security.secTypeX509Ident);
}
+ if (options.sshTunnel.isSelected()) {
+ if (options.sshUseGateway.isSelected()) {
+ String user = options.sshUser.getText();
+ String host = options.sshHost.getText();
+ String port = options.sshPort.getText();
+ viewer.via.setParam(user+"@"+host+":"+port);
+ } else {
+ viewer.tunnel.setParam(true);
+ }
+ }
+ viewer.extSSH.setParam(options.sshUseExt.isSelected());
+ viewer.extSSHClient.setParam(options.sshClient.getText());
+ if (options.sshArgsCustom.isSelected())
+ viewer.extSSHArgs.setParam(options.sshArguments.getText());
+ viewer.sshConfig.setParam(options.sshConfig.getText());
+ viewer.sshKeyFile.setParam(options.sshKeyFile.getText());
}
String desktopSize = (options.desktopSize.isSelected()) ?
options.desktopWidth.getText() + "x" + options.desktopHeight.getText() : "";
// the following are only ever accessed by the GUI thread:
int buttonMask;
- private String serverHost;
- private int serverPort;
private Socket sock;
protected DesktopWindow desktop;
for (Component ch : c.getComponents()) {
if (ch instanceof JCheckBox)
((JCheckBox)ch).addItemListener(this);
+ else if (ch instanceof JRadioButton)
+ ((JRadioButton)ch).addActionListener(this);
else if (ch instanceof JButton)
((JButton)ch).addActionListener(this);
else if (ch instanceof JComboBox)
import java.util.*;
import java.util.Map.Entry;
-
import com.tigervnc.rfb.*;
import static java.awt.GridBagConstraints.BOTH;
private class IntegerDocument extends PlainDocument {
private int limit;
- IntegerDocument(int limit) {
+ public IntegerDocument(int max) {
super();
- this.limit = limit;
+ limit = max;
}
- public void insertString(int offset, String str, AttributeSet a)
+ public void insertString(int offset, String str, AttributeSet a)
throws BadLocationException {
if (str == null || !str.matches("^[0-9]+$")) return;
if ((getLength() + str.length()) > limit)
- java.awt.Toolkit.getDefaultToolkit().beep();
+ Toolkit.getDefaultToolkit().beep();
else
super.insertString(offset, str, a);
}
private class IntegerTextField extends JFormattedTextField {
public IntegerTextField(int digits) {
super();
- this.setDocument(new IntegerDocument(digits));
+ setDocument(new IntegerDocument(digits));
Font f = getFont();
String template = String.format("%0"+digits+"d", 0);
int w = getFontMetrics(f).stringWidth(template) +
- getMargin().left + getMargin().right +
+ getMargin().left + getMargin().right +
getInsets().left + getInsets().right;
int h = getPreferredSize().height;
setPreferredSize(new Dimension(w, h));
CConn cc;
@SuppressWarnings({"rawtypes"})
JComboBox menuKey, compressLevel, qualityLevel, scalingFactor;
- ButtonGroup encodingGroup, colourGroup;
+ ButtonGroup encodingGroup, colourGroup, sshArgsGroup;
JRadioButton zrle, hextile, tight, raw, fullColour, mediumColour,
- lowColour, veryLowColour;
+ lowColour, veryLowColour, sshArgsDefault, sshArgsCustom;
JCheckBox autoSelect, customCompressLevel, noJpeg, viewOnly,
acceptClipboard, sendClipboard, acceptBell, desktopSize,
fullScreen, fullScreenAllMonitors, shared, useLocalCursor,
secVeNCrypt, encNone, encTLS, encX509, secNone, secVnc,
- secPlain, secIdent, sendLocalUsername;
+ secPlain, secIdent, sendLocalUsername, sshTunnel, sshUseExt,
+ sshUseGateway;
JButton okButton, cancelButton, caButton, crlButton, cfLoadButton,
- cfSaveAsButton, defSaveButton, defReloadButton, defClearButton;
- JTextField desktopWidth, desktopHeight, x509ca, x509crl;
+ cfSaveAsButton, defSaveButton, defReloadButton, defClearButton,
+ sshConfigBrowser, sshKeyFileBrowser, sshClientBrowser;
+ JTextField desktopWidth, desktopHeight, x509ca, x509crl, sshUser, sshHost,
+ sshPort, sshClient, sshArguments, sshConfig, sshKeyFile;
JTabbedPane tabPane;
@SuppressWarnings({"rawtypes","unchecked"})
encodingGroup = new ButtonGroup();
colourGroup = new ButtonGroup();
+ sshArgsGroup = new ButtonGroup();
int indent = 0;
// Compression tab
new Insets(0, 0, 0, 0),
NONE, NONE));
+ // SSH tab
+ JPanel sshPanel = new JPanel(new GridBagLayout());
+ sshPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
+ sshTunnel = new JCheckBox("Tunnel VNC over SSH");
+
+ JPanel tunnelPanel = new JPanel(new GridBagLayout());
+
+ sshUseGateway = new JCheckBox("Use SSH gateway");
+ JLabel sshUserLabel = new JLabel("Username");
+ sshUser = new JTextField();
+ JLabel sshUserAtLabel = new JLabel("@");
+ JLabel sshHostLabel = new JLabel("Hostname (or IP address)");
+ sshHost = new JTextField("");
+ JLabel sshPortLabel = new JLabel("Port");
+ sshPort = new IntegerTextField(5);
+
+ sshUseExt = new JCheckBox("Use external SSH client");
+ sshClient = new JTextField();
+ sshClient.setName(Configuration.getParam("extSSHClient").getName());
+ sshClientBrowser = new JButton("Browse");
+ JLabel sshConfigLabel = new JLabel("SSH config file");
+ sshConfig = new JTextField();
+ sshConfig.setName(Configuration.getParam("sshConfig").getName());
+ sshConfigBrowser = new JButton("Browse");
+ JLabel sshKeyFileLabel = new JLabel("SSH identity file");
+ sshKeyFile = new JTextField();
+ sshKeyFile.setName(Configuration.getParam("sshKeyFile").getName());
+ sshKeyFileBrowser = new JButton("Browse");
+ JPanel sshArgsPanel = new JPanel(new GridBagLayout());
+ JLabel sshArgsLabel = new JLabel("Arguments:");
+ sshArgsDefault =
+ new GroupedJRadioButton("Default", sshArgsGroup, sshArgsPanel);
+ sshArgsCustom =
+ new GroupedJRadioButton("Custom", sshArgsGroup, sshArgsPanel);
+ sshArguments = new JTextField();
+
+ JPanel gatewayPanel = new JPanel(new GridBagLayout());
+ gatewayPanel.add(sshUseGateway,
+ new GridBagConstraints(0, 0,
+ REMAINDER, 1,
+ LIGHT, LIGHT,
+ LINE_START, NONE,
+ new Insets(0, 0, 4, 0),
+ NONE, NONE));
+ indent = getButtonLabelInset(sshUseGateway);
+ gatewayPanel.add(sshUserLabel,
+ new GridBagConstraints(0, 1,
+ 1, 1,
+ LIGHT, LIGHT,
+ LINE_START, HORIZONTAL,
+ new Insets(0, indent, 4, 0),
+ NONE, NONE));
+ gatewayPanel.add(sshHostLabel,
+ new GridBagConstraints(2, 1,
+ 1, 1,
+ HEAVY, LIGHT,
+ LINE_START, HORIZONTAL,
+ new Insets(0, 0, 4, 0),
+ NONE, NONE));
+ gatewayPanel.add(sshPortLabel,
+ new GridBagConstraints(3, 1,
+ 1, 1,
+ LIGHT, LIGHT,
+ LINE_START, HORIZONTAL,
+ new Insets(0, 5, 4, 0),
+ NONE, NONE));
+ gatewayPanel.add(sshUser,
+ new GridBagConstraints(0, 2,
+ 1, 1,
+ LIGHT, LIGHT,
+ LINE_START, HORIZONTAL,
+ new Insets(0, indent, 0, 0),
+ NONE, NONE));
+ gatewayPanel.add(sshUserAtLabel,
+ new GridBagConstraints(1, 2,
+ 1, 1,
+ LIGHT, LIGHT,
+ LINE_START, HORIZONTAL,
+ new Insets(0, 2, 0, 2),
+ NONE, NONE));
+ gatewayPanel.add(sshHost,
+ new GridBagConstraints(2, 2,
+ 1, 1,
+ HEAVY, LIGHT,
+ LINE_START, HORIZONTAL,
+ new Insets(0, 0, 0, 0),
+ NONE, NONE));
+ gatewayPanel.add(sshPort,
+ new GridBagConstraints(3, 2,
+ 1, 1,
+ LIGHT, LIGHT,
+ LINE_START, HORIZONTAL,
+ new Insets(0, 5, 0, 0),
+ NONE, NONE));
+
+ JPanel clientPanel = new JPanel(new GridBagLayout());
+ clientPanel.add(sshUseExt,
+ new GridBagConstraints(0, 0,
+ 1, 1,
+ LIGHT, LIGHT,
+ LINE_START, NONE,
+ new Insets(0, 0, 0, 0),
+ NONE, NONE));
+ clientPanel.add(sshClient,
+ new GridBagConstraints(1, 0,
+ 1, 1,
+ HEAVY, LIGHT,
+ LINE_START, HORIZONTAL,
+ new Insets(0, 5, 0, 0),
+ NONE, NONE));
+ clientPanel.add(sshClientBrowser,
+ new GridBagConstraints(2, 0,
+ 1, 1,
+ LIGHT, LIGHT,
+ LINE_START, NONE,
+ new Insets(0, 5, 0, 0),
+ NONE, NONE));
+ sshArgsPanel.add(sshArgsLabel,
+ new GridBagConstraints(0, 1,
+ 1, 1,
+ LIGHT, LIGHT,
+ LINE_START, NONE,
+ new Insets(0, 0, 0, 0),
+ NONE, NONE));
+ sshArgsPanel.add(sshArgsDefault,
+ new GridBagConstraints(1, 1,
+ 1, 1,
+ LIGHT, LIGHT,
+ LINE_START, NONE,
+ new Insets(0, 5, 0, 0),
+ NONE, NONE));
+ sshArgsPanel.add(sshArgsCustom,
+ new GridBagConstraints(2, 1,
+ 1, 1,
+ LIGHT, LIGHT,
+ LINE_START, NONE,
+ new Insets(0, 5, 0, 0),
+ NONE, NONE));
+ sshArgsPanel.add(sshArguments,
+ new GridBagConstraints(3, 1,
+ 1, 1,
+ HEAVY, LIGHT,
+ LINE_START, HORIZONTAL,
+ new Insets(0, 5, 0, 0),
+ NONE, NONE));
+ indent = getButtonLabelInset(sshUseExt);
+ clientPanel.add(sshArgsPanel,
+ new GridBagConstraints(0, 1,
+ REMAINDER, 1,
+ LIGHT, LIGHT,
+ LINE_START, HORIZONTAL,
+ new Insets(4, indent, 0, 0),
+ NONE, NONE));
+
+ JPanel opensshPanel = new JPanel(new GridBagLayout());
+ opensshPanel.setBorder(BorderFactory.createTitledBorder("Embedded SSH client configuration"));
+ opensshPanel.add(sshConfigLabel,
+ new GridBagConstraints(0, 0,
+ 1, 1,
+ LIGHT, LIGHT,
+ LINE_START, NONE,
+ new Insets(0, 0, 5, 0),
+ NONE, NONE));
+ opensshPanel.add(sshConfig,
+ new GridBagConstraints(1, 0,
+ 1, 1,
+ HEAVY, LIGHT,
+ LINE_START, HORIZONTAL,
+ new Insets(0, 5, 5, 0),
+ NONE, NONE));
+ opensshPanel.add(sshConfigBrowser,
+ new GridBagConstraints(2, 0,
+ 1, 1,
+ LIGHT, LIGHT,
+ LINE_START, VERTICAL,
+ new Insets(0, 5, 5, 0),
+ NONE, NONE));
+ opensshPanel.add(sshKeyFileLabel,
+ new GridBagConstraints(0, 1,
+ 1, 1,
+ LIGHT, LIGHT,
+ LINE_START, NONE,
+ new Insets(0, 0, 0, 0),
+ NONE, NONE));
+ opensshPanel.add(sshKeyFile,
+ new GridBagConstraints(1, 1,
+ 1, 1,
+ HEAVY, LIGHT,
+ LINE_START, HORIZONTAL,
+ new Insets(0, 5, 0, 0),
+ NONE, NONE));
+ opensshPanel.add(sshKeyFileBrowser,
+ new GridBagConstraints(2, 1,
+ 1, 1,
+ LIGHT, LIGHT,
+ LINE_START, VERTICAL,
+ new Insets(0, 5, 0, 0),
+ NONE, NONE));
+ tunnelPanel.add(gatewayPanel,
+ new GridBagConstraints(0, 0,
+ REMAINDER, 1,
+ HEAVY, LIGHT,
+ LINE_START, HORIZONTAL,
+ new Insets(0, 0, 4, 0),
+ NONE, NONE));
+ tunnelPanel.add(clientPanel,
+ new GridBagConstraints(0, 1,
+ REMAINDER, 1,
+ HEAVY, LIGHT,
+ LINE_START, HORIZONTAL,
+ new Insets(0, 0, 4, 0),
+ NONE, NONE));
+ tunnelPanel.add(opensshPanel,
+ new GridBagConstraints(0, 2,
+ REMAINDER, 1,
+ HEAVY, LIGHT,
+ LINE_START, HORIZONTAL,
+ new Insets(0, 0, 0, 0),
+ NONE, NONE));
+
+ sshPanel.add(sshTunnel,
+ new GridBagConstraints(0, 0,
+ REMAINDER, 1,
+ LIGHT, LIGHT,
+ LINE_START, NONE,
+ new Insets(0, 0, 4, 0),
+ NONE, NONE));
+ indent = getButtonLabelInset(sshTunnel);
+ sshPanel.add(tunnelPanel,
+ new GridBagConstraints(0, 2,
+ REMAINDER, 1,
+ LIGHT, LIGHT,
+ LINE_START, HORIZONTAL,
+ new Insets(0, indent, 4, 0),
+ NONE, NONE));
+ sshPanel.add(Box.createRigidArea(new Dimension(5, 0)),
+ new GridBagConstraints(0, RELATIVE,
+ REMAINDER, REMAINDER,
+ HEAVY, HEAVY,
+ LINE_START, BOTH,
+ new Insets(0, 0, 0, 0),
+ NONE, NONE));
// load/save tab
JPanel loadSavePanel = new JPanel(new GridBagLayout());
tabPane.addTab("Input", inputPanel);
tabPane.addTab("Screen", ScreenPanel);
tabPane.addTab("Misc", MiscPanel);
+ tabPane.addTab("SSH", sshPanel);
tabPane.addTab("Load / Save", loadSavePanel);
tabPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
// Resize the tabPane if necessary to prevent scrolling
veryLowColour.setEnabled(!autoSelect.isSelected());
compressLevel.setEnabled(customCompressLevel.isSelected());
qualityLevel.setEnabled(noJpeg.isSelected());
- sendLocalUsername.setEnabled(secVeNCrypt.isEnabled()&&
- (secPlain.isSelected()||secIdent.isSelected()));
+ sendLocalUsername.setEnabled(secVeNCrypt.isEnabled() &&
+ (secPlain.isSelected() || secIdent.isSelected()));
+ sshArguments.setEnabled(sshTunnel.isSelected() &&
+ (sshUseExt.isSelected() && sshArgsCustom.isSelected()));
}
private void updatePreferences() {
if (!CSecurityTLS.x509crl.getValueStr().equals(""))
UserPreferences.set("viewer", "x509crl",
CSecurityTLS.x509crl.getValueStr());
+ UserPreferences.set("global", "Tunnel", sshTunnel.isSelected());
+ if (sshUseGateway.isSelected()) {
+ String via = sshUser.getText()+"@"+sshHost.getText()+":"+sshPort.getText();
+ UserPreferences.set("global", "Via", via);
+ }
+ if (sshUseExt.isSelected()) {
+ UserPreferences.set("global", "extSSH", sshUseExt.isSelected());
+ UserPreferences.set("global", "extSSHClient", sshClient.getText());
+ if (!sshArguments.getText().isEmpty())
+ UserPreferences.set("global", "extSSHArgs", sshArguments.getText());
+ }
+ UserPreferences.set("global", "SSHConfig", sshConfig.getText());
+ UserPreferences.set("global", "SSHKeyFile", sshKeyFile.getText());
}
private void restorePreferences() {
sendClipboard.setSelected(UserPreferences.getBool("global",
"SendClipboard"));
menuKey.setSelectedItem(UserPreferences.get("global", "MenuKey"));
- desktopSize.setSelected(UserPreferences.get("global", "DesktopSize")
- != null);
+ desktopSize.setSelected(!UserPreferences.get("global", "DesktopSize").isEmpty());
if (desktopSize.isSelected()) {
String desktopSizeString = UserPreferences.get("global", "DesktopSize");
desktopWidth.setText(desktopSizeString.split("x")[0]);
secNone.setSelected(UserPreferences.getBool("viewer", "secNone", true));
if (secVnc.isEnabled())
secVnc.setSelected(UserPreferences.getBool("viewer", "secVnc", true));
+ sshTunnel.setSelected(UserPreferences.getBool("global", "Tunnel"));
+ sshUseGateway.setSelected(UserPreferences.get("global", "Via") != null);
+ if (sshUseGateway.isSelected())
+ cc.viewer.via.setParam(UserPreferences.get("global", "Via"));
+ sshUser.setText(Tunnel.getSshUser(cc));
+ sshHost.setText(Tunnel.getSshHost(cc));
+ sshPort.setText(Integer.toString(Tunnel.getSshPort(cc)));
+ sshUseExt.setSelected(UserPreferences.getBool("global", "extSSH"));
+ File f = new File(UserPreferences.get("global", "extSSHClient"));
+ if (f.exists() && f.canExecute())
+ sshClient.setText(f.getAbsolutePath());
+ sshArguments.setText(UserPreferences.get("global", "extSSHArgs"));
+ if (sshArguments.getText().isEmpty())
+ sshArgsDefault.setSelected(true);
+ else
+ sshArgsCustom.setSelected(true);
+ f = new File(UserPreferences.get("global", "SSHConfig"));
+ if (f.exists() && f.canRead())
+ sshConfig.setText(f.getAbsolutePath());
+ if (UserPreferences.get("global", "SSHKeyFile") != null) {
+ f = new File(UserPreferences.get("global", "SSHKeyFile"));
+ if (f.exists() && f.canRead())
+ sshKeyFile.setText(f.getAbsolutePath());
+ } else {
+ sshKeyFile.setText(Tunnel.getSshKeyFile(cc));
+ }
+ sshUseGateway.setEnabled(sshTunnel.isSelected());
+ sshUser.setEnabled(sshTunnel.isSelected() &&
+ sshUseGateway.isEnabled() &&
+ sshUseGateway.isSelected());
+ sshHost.setEnabled(sshTunnel.isSelected() &&
+ sshUseGateway.isEnabled() &&
+ sshUseGateway.isSelected());
+ sshPort.setEnabled(sshTunnel.isSelected() &&
+ sshUseGateway.isEnabled() &&
+ sshUseGateway.isSelected());
+ sshUseExt.setEnabled(sshTunnel.isSelected());
+ sshClient.setEnabled(sshTunnel.isSelected() &&
+ sshUseExt.isEnabled());
+ sshClientBrowser.setEnabled(sshTunnel.isSelected() &&
+ sshUseExt.isEnabled() &&
+ sshUseExt.isSelected());
+ sshArgsDefault.setEnabled(sshTunnel.isSelected() &&
+ sshUseExt.isEnabled() &&
+ sshUseExt.isSelected());
+ sshArgsCustom.setEnabled(sshTunnel.isSelected() &&
+ sshUseExt.isEnabled() &&
+ sshUseExt.isSelected());
+ sshArguments.setEnabled(sshTunnel.isSelected() &&
+ sshUseExt.isEnabled() &&
+ sshUseExt.isSelected() &&
+ sshArgsCustom.isSelected());
+ sshConfig.setEnabled(sshTunnel.isSelected() &&
+ sshUseExt.isEnabled() &&
+ !sshUseExt.isSelected());
+ sshConfigBrowser.setEnabled(sshTunnel.isSelected() &&
+ sshUseExt.isEnabled() &&
+ !sshUseExt.isSelected());
+ sshKeyFile.setEnabled(sshTunnel.isSelected() &&
+ sshUseExt.isEnabled() &&
+ !sshUseExt.isSelected());
+ sshKeyFileBrowser.setEnabled(sshTunnel.isSelected() &&
+ sshUseExt.isEnabled() &&
+ !sshUseExt.isSelected());
}
public void endDialog() {
JButton button = (JButton)s;
if (button == okButton) {
JTextField[] fields =
- { x509ca, x509crl };
+ { x509ca, x509crl, sshClient, sshConfig, sshKeyFile };
for (JTextField field : fields) {
if (field.getText() != null && !field.getText().equals("")) {
File f = new File(field.getText());
if (!f.exists() || !f.canRead()) {
String msg = new String("The file "+f.getAbsolutePath()+
" specified for option "+field.getName()+
- " does not exist or cannot be read. Please "+
- "correct before proceeding.");
+ " does not exist or cannot be read. Please"+
+ " correct before proceeding.");
JOptionPane.showMessageDialog(this, msg, "WARNING",
JOptionPane.WARNING_MESSAGE);
return;
int ret = fc.showOpenDialog(this);
if (ret == JFileChooser.APPROVE_OPTION)
x509crl.setText(fc.getSelectedFile().toString());
+ } else if (button == sshClientBrowser) {
+ JFileChooser fc = new JFileChooser();
+ fc.setDialogTitle("Path to external SSH client");
+ fc.setApproveButtonText("OK");
+ fc.setFileHidingEnabled(false);
+ int ret = fc.showOpenDialog(this);
+ if (ret == JFileChooser.APPROVE_OPTION)
+ sshClient.setText(fc.getSelectedFile().toString());
+ } else if (button == sshConfigBrowser) {
+ JFileChooser fc = new JFileChooser();
+ fc.setDialogTitle("Path to OpenSSH client config file");
+ fc.setApproveButtonText("OK");
+ fc.setFileHidingEnabled(false);
+ int ret = fc.showOpenDialog(this);
+ if (ret == JFileChooser.APPROVE_OPTION)
+ sshConfig.setText(fc.getSelectedFile().toString());
+ } else if (button == sshKeyFileBrowser) {
+ JFileChooser fc = new JFileChooser();
+ fc.setDialogTitle("Path to SSH key file");
+ fc.setApproveButtonText("OK");
+ fc.setFileHidingEnabled(false);
+ int ret = fc.showOpenDialog(this);
+ if (ret == JFileChooser.APPROVE_OPTION)
+ sshKeyFile.setText(fc.getSelectedFile().toString());
+ }
+ } else if (s instanceof JRadioButton) {
+ JRadioButton button = (JRadioButton)s;
+ if (button == sshArgsCustom || button == sshArgsDefault) {
+ sshArguments.setEnabled(sshArgsCustom.isSelected());
}
}
}
qualityLevel.setEnabled(enable);
} else if (item == encX509) {
x509ca.setEnabled(enable);
- x509crl.setEnabled(enable);
caButton.setEnabled(enable);
+ x509crl.setEnabled(enable);
crlButton.setEnabled(enable);
} else if (item == secVeNCrypt) {
encNone.setEnabled(enable);
encTLS.setEnabled(enable);
encX509.setEnabled(enable);
x509ca.setEnabled(enable && encX509.isSelected());
- x509crl.setEnabled(enable && encX509.isSelected());
caButton.setEnabled(enable && encX509.isSelected());
+ x509crl.setEnabled(enable && encX509.isSelected());
crlButton.setEnabled(enable && encX509.isSelected());
secIdent.setEnabled(enable);
secPlain.setEnabled(enable);
} else if (item == secIdent || item == secPlain) {
sendLocalUsername.setEnabled(secIdent.isSelected() ||
secPlain.isSelected());
+ } else if (item == sshTunnel) {
+ sshUseGateway.setEnabled(enable);
+ sshUser.setEnabled(enable &&
+ sshUseGateway.isEnabled() &&
+ sshUseGateway.isSelected());
+ sshHost.setEnabled(enable &&
+ sshUseGateway.isEnabled() &&
+ sshUseGateway.isSelected());
+ sshPort.setEnabled(enable &&
+ sshUseGateway.isEnabled() &&
+ sshUseGateway.isSelected());
+ sshUseExt.setEnabled(enable);
+ sshClient.setEnabled(enable &&
+ sshUseExt.isEnabled() &&
+ sshUseExt.isSelected());
+ sshClientBrowser.setEnabled(enable &&
+ sshUseExt.isEnabled() &&
+ sshUseExt.isSelected());
+ sshArgsDefault.setEnabled(enable &&
+ sshUseExt.isEnabled() &&
+ sshUseExt.isSelected());
+ sshArgsCustom.setEnabled(enable &&
+ sshUseExt.isEnabled() &&
+ sshUseExt.isSelected());
+ sshArguments.setEnabled(enable &&
+ sshUseExt.isEnabled() &&
+ sshUseExt.isSelected() &&
+ sshArgsCustom.isSelected());
+ sshConfig.setEnabled(enable &&
+ sshUseExt.isEnabled() &&
+ !sshUseExt.isSelected());
+ sshConfigBrowser.setEnabled(enable &&
+ sshUseExt.isEnabled() &&
+ !sshUseExt.isSelected());
+ sshKeyFile.setEnabled(enable &&
+ sshUseExt.isEnabled() &&
+ !sshUseExt.isSelected());
+ sshKeyFileBrowser.setEnabled(enable &&
+ sshUseExt.isEnabled() &&
+ !sshUseExt.isSelected());
+ } else if (item == sshUseExt) {
+ sshClient.setEnabled(enable);
+ sshClientBrowser.setEnabled(enable);
+ sshArgsDefault.setEnabled(enable);
+ sshArgsCustom.setEnabled(enable);
+ sshArguments.setEnabled(enable && sshArgsCustom.isSelected());
+ sshConfig.setEnabled(!enable);
+ sshConfigBrowser.setEnabled(!enable);
+ sshKeyFile.setEnabled(!enable);
+ sshKeyFileBrowser.setEnabled(!enable);
+ } else if (item == sshUseGateway) {
+ sshUser.setEnabled(enable);
+ sshHost.setEnabled(enable);
+ sshPort.setEnabled(enable);
}
}
}
if (userEntry.isEnabled())
if (userEntry.getText().equals(""))
return false;
+ else if (!passwdEntry.isEnabled())
+ return true;
if (passwdEntry.isEnabled())
if (!passwdEntry.getText().equals(""))
return true;
--- /dev/null
+/*
+ * Copyright (C) 2012-2016 All Rights Reserved.
+ * Copyright (C) 2000 Const Kaplinsky. All Rights Reserved.
+ * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
+ *
+ * This 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 this software; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+/*
+ * tunnel.java - SSH tunneling support
+ */
+
+package com.tigervnc.vncviewer;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.*;
+
+import com.tigervnc.rdr.*;
+import com.tigervnc.rfb.*;
+import com.tigervnc.rfb.Exception;
+import com.tigervnc.network.*;
+
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.ConfigRepository;
+import com.jcraft.jsch.Logger;
+import com.jcraft.jsch.OpenSSHConfig;
+import com.jcraft.jsch.Session;
+
+public class Tunnel {
+
+ private final static String DEFAULT_TUNNEL_TEMPLATE
+ = "-f -L %L:localhost:%R %H sleep 20";
+ private final static String DEFAULT_VIA_TEMPLATE
+ = "-f -L %L:%H:%R %G sleep 20";
+
+ public static void createTunnel(CConn cc, int localPort) throws Exception {
+ int remotePort;
+ String gatewayHost;
+ String remoteHost;
+
+ remotePort = cc.getServerPort();
+ if (cc.viewer.tunnel.getValue()) {
+ gatewayHost = cc.getServerName();
+ remoteHost = "localhost";
+ } else {
+ gatewayHost = getSshHost(cc);
+ remoteHost = cc.getServerName();
+ }
+
+ String pattern = cc.viewer.extSSHArgs.getValue();
+ if (pattern == null) {
+ if (cc.viewer.tunnel.getValue())
+ pattern = System.getProperty("VNC_TUNNEL_CMD");
+ else
+ pattern = System.getProperty("VNC_VIA_CMD");
+ }
+
+ if (cc.viewer.extSSH.getValue() ||
+ (pattern != null && pattern.length() > 0)) {
+ createTunnelExt(gatewayHost, remoteHost, remotePort, localPort, pattern, cc);
+ } else {
+ createTunnelJSch(gatewayHost, remoteHost, remotePort, localPort, cc);
+ }
+ }
+
+ private static class MyJSchLogger implements Logger {
+ public boolean isEnabled(int level){
+ return true;
+ }
+
+ public void log(int level, String msg){
+ switch (level) {
+ case Logger.INFO:
+ vlog.info(msg);
+ break;
+ case Logger.ERROR:
+ vlog.error(msg);
+ break;
+ default:
+ vlog.debug(msg);
+ }
+ }
+ }
+
+ public static String getSshHost(CConn cc) {
+ String sshHost = cc.viewer.via.getValue();
+ if (sshHost == null)
+ return cc.getServerName();
+ int end = sshHost.indexOf(":");
+ if (end < 0)
+ end = sshHost.length();
+ sshHost = sshHost.substring(sshHost.indexOf("@")+1, end);
+ return sshHost;
+ }
+
+ public static String getSshUser(CConn cc) {
+ String sshUser = (String)System.getProperties().get("user.name");
+ String via = cc.viewer.via.getValue();
+ if (via != null && via.indexOf("@") > 0)
+ sshUser = via.substring(0, via.indexOf("@"));
+ return sshUser;
+ }
+
+ public static int getSshPort(CConn cc) {
+ String sshPort = "22";
+ String via = cc.viewer.via.getValue();
+ if (via != null && via.indexOf(":") > 0)
+ sshPort = via.substring(via.indexOf(":")+1, via.length());
+ return Integer.parseInt(sshPort);
+ }
+
+ public static String getSshKeyFile(CConn cc) {
+ if (cc.viewer.sshKeyFile.getValue() != null)
+ return cc.viewer.sshKeyFile.getValue();
+ String[] ids = { "id_dsa", "id_rsa" };
+ for (String id : ids) {
+ File f = new File(FileUtils.getHomeDir()+".ssh/"+id);
+ if (f.exists() && f.canRead())
+ return(f.getAbsolutePath());
+ }
+ return null;
+ }
+
+ private static void createTunnelJSch(String gatewayHost, String remoteHost,
+ int remotePort, int localPort,
+ CConn cc) throws Exception {
+ JSch.setLogger(new MyJSchLogger());
+ JSch jsch=new JSch();
+
+ try {
+ // NOTE: jsch does not support all ciphers. User may be
+ // prompted to accept host key authenticy even if
+ // the key is in the known_hosts file.
+ File knownHosts = new File(FileUtils.getHomeDir()+".ssh/known_hosts");
+ if (knownHosts.exists() && knownHosts.canRead())
+ jsch.setKnownHosts(knownHosts.getAbsolutePath());
+ ArrayList<File> privateKeys = new ArrayList<File>();
+ String sshKeyFile = cc.options.sshKeyFile.getText();
+ String sshKey = cc.viewer.sshKey.getValue();
+ if (sshKey != null) {
+ String sshKeyPass = cc.viewer.sshKeyPass.getValue();
+ byte[] keyPass = null, key;
+ if (sshKeyPass != null)
+ keyPass = sshKeyPass.getBytes();
+ sshKey = sshKey.replaceAll("\\\\n", "\n");
+ key = sshKey.getBytes();
+ jsch.addIdentity("TigerVNC", key, null, keyPass);
+ } else if (!sshKeyFile.equals("")) {
+ File f = new File(sshKeyFile);
+ if (!f.exists() || !f.canRead())
+ throw new Exception("Cannot access SSH key file "+ sshKeyFile);
+ privateKeys.add(f);
+ }
+ for (Iterator<File> i = privateKeys.iterator(); i.hasNext();) {
+ File privateKey = (File)i.next();
+ if (privateKey.exists() && privateKey.canRead())
+ if (cc.viewer.sshKeyPass.getValue() != null)
+ jsch.addIdentity(privateKey.getAbsolutePath(),
+ cc.viewer.sshKeyPass.getValue());
+ else
+ jsch.addIdentity(privateKey.getAbsolutePath());
+ }
+
+ String user = getSshUser(cc);
+ String label = new String("SSH Authentication");
+ PasswdDialog dlg =
+ new PasswdDialog(label, (user == null ? false : true), false);
+ dlg.userEntry.setText(user != null ? user : "");
+ File ssh_config = new File(cc.viewer.sshConfig.getValue());
+ if (ssh_config.exists() && ssh_config.canRead()) {
+ ConfigRepository repo =
+ OpenSSHConfig.parse(ssh_config.getAbsolutePath());
+ jsch.setConfigRepository(repo);
+ }
+ Session session=jsch.getSession(user, gatewayHost, getSshPort(cc));
+ session.setUserInfo(dlg);
+ // OpenSSHConfig doesn't recognize StrictHostKeyChecking
+ if (session.getConfig("StrictHostKeyChecking") == null)
+ session.setConfig("StrictHostKeyChecking", "ask");
+ session.connect();
+ session.setPortForwardingL(localPort, remoteHost, remotePort);
+ } catch (java.lang.Exception e) {
+ throw new Exception(e.getMessage());
+ }
+ }
+
+ private static class MyExtProcess implements Runnable {
+
+ private String cmd = null;
+ private Process pid = null;
+
+ private static class MyProcessLogger extends Thread {
+ private final BufferedReader err;
+
+ public MyProcessLogger(Process p) {
+ InputStreamReader reader =
+ new InputStreamReader(p.getErrorStream());
+ err = new BufferedReader(reader);
+ }
+
+ @Override
+ public void run() {
+ try {
+ while (true) {
+ String msg = err.readLine();
+ if (msg != null)
+ vlog.info(msg);
+ }
+ } catch(java.io.IOException e) {
+ vlog.info(e.getMessage());
+ } finally {
+ try {
+ if (err != null)
+ err.close();
+ } catch (java.io.IOException e ) { }
+ }
+ }
+ }
+
+ private static class MyShutdownHook extends Thread {
+
+ private Process proc = null;
+
+ public MyShutdownHook(Process p) {
+ proc = p;
+ }
+
+ @Override
+ public void run() {
+ try {
+ proc.exitValue();
+ } catch (IllegalThreadStateException e) {
+ try {
+ // wait for CConn to shutdown the socket
+ Thread.sleep(500);
+ } catch(InterruptedException ie) { }
+ proc.destroy();
+ }
+ }
+ }
+
+ public MyExtProcess(String command) {
+ cmd = command;
+ }
+
+ public void run() {
+ try {
+ Runtime runtime = Runtime.getRuntime();
+ pid = runtime.exec(cmd);
+ runtime.addShutdownHook(new MyShutdownHook(pid));
+ new MyProcessLogger(pid).start();
+ pid.waitFor();
+ } catch(InterruptedException e) {
+ vlog.info(e.getMessage());
+ } catch(java.io.IOException e) {
+ vlog.info(e.getMessage());
+ }
+ }
+ }
+
+ private static void createTunnelExt(String gatewayHost, String remoteHost,
+ int remotePort, int localPort,
+ String pattern, CConn cc) throws Exception {
+ if (pattern == null || pattern.length() < 1) {
+ if (cc.viewer.tunnel.getValue())
+ pattern = DEFAULT_TUNNEL_TEMPLATE;
+ else
+ pattern = DEFAULT_VIA_TEMPLATE;
+ }
+ String cmd = fillCmdPattern(pattern, gatewayHost, remoteHost,
+ remotePort, localPort, cc);
+ try {
+ Thread t = new Thread(new MyExtProcess(cmd));
+ t.start();
+ // wait for the ssh process to start
+ Thread.sleep(1000);
+ } catch (java.lang.Exception e) {
+ throw new Exception(e.getMessage());
+ }
+ }
+
+ private static String fillCmdPattern(String pattern, String gatewayHost,
+ String remoteHost, int remotePort,
+ int localPort, CConn cc) {
+ boolean H_found = false, G_found = false, R_found = false, L_found = false;
+ boolean P_found = false;
+ String cmd = cc.options.sshClient.getText() + " ";
+ pattern.replaceAll("^\\s+", "");
+
+ String user = getSshUser(cc);
+ int sshPort = getSshPort(cc);
+ gatewayHost = user + "@" + gatewayHost;
+
+ for (int i = 0; i < pattern.length(); i++) {
+ if (pattern.charAt(i) == '%') {
+ switch (pattern.charAt(++i)) {
+ case 'H':
+ cmd += (cc.viewer.tunnel.getValue() ? gatewayHost : remoteHost);
+ H_found = true;
+ continue;
+ case 'G':
+ cmd += gatewayHost;
+ G_found = true;
+ continue;
+ case 'R':
+ cmd += remotePort;
+ R_found = true;
+ continue;
+ case 'L':
+ cmd += localPort;
+ L_found = true;
+ continue;
+ case 'P':
+ cmd += sshPort;
+ P_found = true;
+ continue;
+ }
+ }
+ cmd += pattern.charAt(i);
+ }
+
+ if (pattern.length() > 1024)
+ throw new Exception("Tunneling command is too long.");
+
+ if (!H_found || !R_found || !L_found)
+ throw new Exception("%H, %R or %L absent in tunneling command template.");
+
+ if (!cc.viewer.tunnel.getValue() && !G_found)
+ throw new Exception("%G pattern absent in tunneling command template.");
+
+ vlog.info("SSH command line: "+cmd);
+ if (VncViewer.os.startsWith("windows"))
+ cmd.replaceAll("\\\\", "\\\\\\\\");
+ return cmd;
+ }
+
+ static LogWriter vlog = new LogWriter("Tunnel");
+}
continue;
}
- if (argv[i].equalsIgnoreCase("-tunnel") || argv[i].equalsIgnoreCase("-via")) {
- if (!tunnel.createTunnel(argv.length, argv, i))
- exit(1);
- if (argv[i].equalsIgnoreCase("-via")) i++;
- continue;
- }
-
if (Configuration.setParam(argv[i]))
continue;
"Produce a system beep when requested to by the server.",
true);
StringParameter via
- = new StringParameter("via",
+ = new StringParameter("Via",
"Automatically create an encrypted TCP tunnel to "+
- "machine gateway, then use that tunnel to connect "+
- "to a VNC server running on host. By default, "+
- "this option invokes SSH local port forwarding and "+
- "assumes that the SSH client binary is located at "+
- "/usr/bin/ssh. Note that when using the -via "+
- "option, the host machine name should be specified "+
- "from the point of view of the gateway machine. "+
- "For example, \"localhost\" denotes the gateway, "+
- "not the machine on which vncviewer was launched. "+
+ "the gateway machine, then connect to the VNC host "+
+ "through that tunnel. By default, this option invokes "+
+ "SSH local port forwarding using the embedded JSch "+
+ "client, however an external SSH client may be specified "+
+ "using the \"-extSSH\" parameter. Note that when using "+
+ "the -via option, the VNC host machine name should be "+
+ "specified from the point of view of the gateway machine, "+
+ "e.g. \"localhost\" denotes the gateway, "+
+ "not the machine on which the viewer was launched. "+
"See the System Properties section below for "+
- "information on configuring the -via option.",
+ "information on configuring the -Via option.", null);
+ BoolParameter tunnel
+ = new BoolParameter("Tunnel",
+ "The -Tunnel command is basically a shorthand for the "+
+ "-via command when the VNC server and SSH gateway are "+
+ "one and the same. -Tunnel creates an SSH connection "+
+ "to the server and forwards the VNC through the tunnel "+
+ "without the need to specify anything else.", false);
+ BoolParameter extSSH
+ = new BoolParameter("extSSH",
+ "By default, SSH tunneling uses the embedded JSch client "+
+ "for tunnel creation. This option causes the client to "+
+ "invoke an external SSH client application for all tunneling "+
+ "operations. By default, \"/usr/bin/ssh\" is used, however "+
+ "the path to the external application may be specified using "+
+ "the -SSHClient option.", false);
+ StringParameter extSSHClient
+ = new StringParameter("extSSHClient",
+ "Specifies the path to an external SSH client application "+
+ "that is to be used for tunneling operations when the -extSSH "+
+ "option is in effect.", "/usr/bin/ssh");
+ StringParameter extSSHArgs
+ = new StringParameter("extSSHArgs",
+ "Specifies the arguments string or command template to be used "+
+ "by the external SSH client application when the -extSSH option "+
+ "is in effect. The string will be processed according to the same "+
+ "pattern substitution rules as the VNC_TUNNEL_CMD and VNC_VIA_CMD "+
+ "system properties, and can be used to override those in a more "+
+ "command-line friendly way. If not specified, then the appropriate "+
+ "VNC_TUNNEL_CMD or VNC_VIA_CMD command template will be used.", null);
+ StringParameter sshConfig
+ = new StringParameter("SSHConfig",
+ "Specifies the path to an OpenSSH configuration file that to "+
+ "be parsed by the embedded JSch SSH client during tunneling "+
+ "operations.", FileUtils.getHomeDir()+".ssh/config");
+ StringParameter sshKey
+ = new StringParameter("SSHKey",
+ "When using the Via or Tunnel options with the embedded SSH client, "+
+ "this parameter specifies the text of the SSH private key to use when "+
+ "authenticating with the SSH server. You can use \\n within the string "+
+ "to specify a new line.", null);
+ StringParameter sshKeyFile
+ = new StringParameter("SSHKeyFile",
+ "When using the Via or Tunnel options with the embedded SSH client, "+
+ "this parameter specifies a file that contains an SSH private key "+
+ "(or keys) to use when authenticating with the SSH server. If not "+
+ "specified, ~/.ssh/id_dsa or ~/.ssh/id_rsa will be used (if they exist). "+
+ "Otherwise, the client will fallback to prompting for an SSH password.",
null);
-
- StringParameter tunnelMode
- = new StringParameter("tunnel",
- "Automatically create an encrypted TCP tunnel to "+
- "remote gateway, then use that tunnel to connect "+
- "to the specified VNC server port on the remote "+
- "host. See the System Properties section below "+
- "for information on configuring the -tunnel option.",
- null);
-
+ StringParameter sshKeyPass
+ = new StringParameter("SSHKeyPass",
+ "When using the Via or Tunnel options with the embedded SSH client, "+
+ "this parameter specifies the passphrase for the SSH key.", null);
BoolParameter customCompressLevel
= new BoolParameter("CustomCompressLevel",
"Use custom compression level. "+
+++ /dev/null
-/*
- * Copyright (C) 2012 Brian P. Hinz. All Rights Reserved.
- * Copyright (C) 2000 Const Kaplinsky. All Rights Reserved.
- * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
- *
- * This 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 of the License, or
- * (at your option) any later version.
- *
- * This software 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 this software; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
- * USA.
- */
-
-/*
- * tunnel.java - SSH tunneling support
- */
-
-package com.tigervnc.vncviewer;
-
-import java.io.File;
-import java.lang.Character;
-import java.util.ArrayList;
-import java.util.Iterator;
-
-import com.tigervnc.rdr.*;
-import com.tigervnc.rfb.*;
-import com.tigervnc.network.*;
-
-import com.jcraft.jsch.JSch;
-import com.jcraft.jsch.Session;
-
-public class tunnel
-{
- private final static Integer SERVER_PORT_OFFSET = 5900;;
- private final static String DEFAULT_SSH_CMD = "/usr/bin/ssh";
- private final static String DEFAULT_TUNNEL_CMD
- = DEFAULT_SSH_CMD+" -f -L %L:localhost:%R %H sleep 20";
- private final static String DEFAULT_VIA_CMD
- = DEFAULT_SSH_CMD+" -f -L %L:%H:%R %G sleep 20";
-
- private final static int H = 17;
- private final static int G = 16;
- private final static int R = 27;
- private final static int L = 21;
-
- /* True if there was -tunnel or -via option in the command line. */
- private static boolean tunnelSpecified = false;
-
- /* True if it was -tunnel, not -via option. */
- private static boolean tunnelOption = false;
-
- /* "Hostname:display" pair in the command line will be substituted
- by this fake argument when tunneling is used. */
- private static String lastArgv;
-
- private static String tunnelEndpoint;
-
- public static Boolean
- createTunnel(int pargc, String[] argv, int tunnelArgIndex)
- {
- char[] pattern;
- char[] cmd = new char[1024];
- int[] localPort = new int[1];
- int[] remotePort = new int[1];
- char[] localPortStr = new char[8];
- char[] remotePortStr = new char[8];
- StringBuilder gatewayHost = new StringBuilder("");
- StringBuilder remoteHost = new StringBuilder("localhost");
-
- tunnelSpecified = true;
- if (argv[tunnelArgIndex].equalsIgnoreCase("-tunnel"))
- tunnelOption = true;
-
- pattern = getCmdPattern();
- if (pattern == null)
- return false;
-
- localPort[0] = TcpSocket.findFreeTcpPort();
- if (localPort[0] == 0)
- return false;
-
- if (tunnelOption) {
- processTunnelArgs(remoteHost, remotePort, localPort,
- pargc, argv, tunnelArgIndex);
- } else {
- processViaArgs(gatewayHost, remoteHost, remotePort, localPort,
- pargc, argv, tunnelArgIndex);
- }
-
- localPortStr = Integer.toString(localPort[0]).toCharArray();
- remotePortStr = Integer.toString(remotePort[0]).toCharArray();
-
- if (!fillCmdPattern(cmd, pattern, gatewayHost.toString().toCharArray(),
- remoteHost.toString().toCharArray(), remotePortStr, localPortStr))
- return false;
-
- if (!runCommand(new String(cmd)))
- return false;
-
- return true;
- }
-
- private static void
- processTunnelArgs(StringBuilder remoteHost, int[] remotePort,
- int[] localPort, int pargc, String[] argv,
- int tunnelArgIndex)
- {
- String pdisplay;
-
- if (tunnelArgIndex >= pargc - 1)
- VncViewer.usage();
-
- pdisplay = argv[pargc - 1].split(":")[1];
- if (pdisplay == null || pdisplay == argv[pargc - 1])
- VncViewer.usage();
-
- if (pdisplay.matches("/[^0-9]/"))
- VncViewer.usage();
-
- remotePort[0] = Integer.parseInt(pdisplay);
- if (remotePort[0] < 100)
- remotePort[0] = remotePort[0] + SERVER_PORT_OFFSET;
-
- lastArgv = new String("localhost::"+localPort[0]);
-
- remoteHost.setLength(0);
- remoteHost.insert(0, argv[pargc - 1].split(":")[0]);
- argv[pargc - 1] = lastArgv;
-
- //removeArgs(pargc, argv, tunnelArgIndex, 1);
- }
-
- private static void
- processViaArgs(StringBuilder gatewayHost, StringBuilder remoteHost,
- int[] remotePort, int[] localPort,
- int pargc, String[] argv, int tunnelArgIndex)
- {
- String colonPos;
- int len, portOffset;
- int disp;
-
- if (tunnelArgIndex >= pargc - 2)
- VncViewer.usage();
-
- colonPos = argv[pargc - 1].split(":", 2)[1];
- if (colonPos == null) {
- /* No colon -- use default port number */
- remotePort[0] = SERVER_PORT_OFFSET;
- } else {
- len = colonPos.length();
- portOffset = SERVER_PORT_OFFSET;
- if (colonPos.startsWith(":")) {
- /* Two colons -- interpret as a port number */
- colonPos.replaceFirst(":", "");
- len--;
- portOffset = 0;
- }
- if (len == 0 || colonPos.matches("/[^0-9]/")) {
- VncViewer.usage();
- }
- disp = Integer.parseInt(colonPos);
- if (portOffset != 0 && disp >= 100)
- portOffset = 0;
- remotePort[0] = disp + portOffset;
- }
-
- lastArgv = "localhost::"+localPort[0];
-
- gatewayHost.setLength(0);
- gatewayHost.insert(0, argv[tunnelArgIndex + 1]);
-
- if (!argv[pargc - 1].split(":", 2)[0].equals("")) {
- remoteHost.setLength(0);
- remoteHost.insert(0, argv[pargc - 1].split(":", 2)[0]);
- }
-
- argv[pargc - 1] = lastArgv;
-
- //removeArgs(pargc, argv, tunnelArgIndex, 2);
- }
-
- private static char[]
- getCmdPattern()
- {
- String pattern = "";
-
- try {
- if (tunnelOption) {
- pattern = System.getProperty("VNC_TUNNEL_CMD");
- } else {
- pattern = System.getProperty("VNC_VIA_CMD");
- }
- } catch (java.lang.Exception e) {
- vlog.info(e.toString());
- }
- if (pattern == null || pattern.equals(""))
- pattern = (tunnelOption) ? DEFAULT_TUNNEL_CMD : DEFAULT_VIA_CMD;
-
- return pattern.toCharArray();
- }
-
- /* Note: in fillCmdPattern() result points to a 1024-byte buffer */
-
- private static boolean
- fillCmdPattern(char[] result, char[] pattern,
- char[] gatewayHost, char[] remoteHost,
- char[] remotePort, char[] localPort)
- {
- int i, j;
- boolean H_found = false, G_found = false, R_found = false, L_found = false;
-
- for (i=0, j=0; i < pattern.length && j<1023; i++, j++) {
- if (pattern[i] == '%') {
- switch (pattern[++i]) {
- case 'H':
- System.arraycopy(remoteHost, 0, result, j, remoteHost.length);
- j += remoteHost.length;
- H_found = true;
- tunnelEndpoint = new String(remoteHost);
- continue;
- case 'G':
- System.arraycopy(gatewayHost, 0, result, j, gatewayHost.length);
- j += gatewayHost.length;
- G_found = true;
- tunnelEndpoint = new String(gatewayHost);
- continue;
- case 'R':
- System.arraycopy(remotePort, 0, result, j, remotePort.length);
- j += remotePort.length;
- R_found = true;
- continue;
- case 'L':
- System.arraycopy(localPort, 0, result, j, localPort.length);
- j += localPort.length;
- L_found = true;
- continue;
- case '\0':
- i--;
- continue;
- }
- }
- result[j] = pattern[i];
- }
-
- if (pattern.length > 1024) {
- vlog.error("Tunneling command is too long.");
- return false;
- }
-
- if (!H_found || !R_found || !L_found) {
- vlog.error("%H, %R or %L absent in tunneling command.");
- return false;
- }
- if (!tunnelOption && !G_found) {
- vlog.error("%G pattern absent in tunneling command.");
- return false;
- }
-
- return true;
- }
-
- private static Boolean
- runCommand(String cmd)
- {
- try{
- JSch jsch=new JSch();
- String homeDir = new String("");
- try {
- homeDir = System.getProperty("user.home");
- } catch(java.security.AccessControlException e) {
- System.out.println("Cannot access user.home system property");
- }
- // NOTE: jsch does not support all ciphers. User may be
- // prompted to accept host key authenticy even if
- // the key is in the known_hosts file.
- File knownHosts = new File(homeDir+"/.ssh/known_hosts");
- if (knownHosts.exists() && knownHosts.canRead())
- jsch.setKnownHosts(knownHosts.getAbsolutePath());
- ArrayList<File> privateKeys = new ArrayList<File>();
- privateKeys.add(new File(homeDir+"/.ssh/id_rsa"));
- privateKeys.add(new File(homeDir+"/.ssh/id_dsa"));
- for (Iterator<File> i = privateKeys.iterator(); i.hasNext();) {
- File privateKey = (File)i.next();
- if (privateKey.exists() && privateKey.canRead())
- jsch.addIdentity(privateKey.getAbsolutePath());
- }
- // username and passphrase will be given via UserInfo interface.
- PasswdDialog dlg = new PasswdDialog(new String("SSH Authentication"), false, false);
- dlg.promptPassword(new String("SSH Authentication"));
-
- Session session=jsch.getSession(dlg.userEntry.getText(), tunnelEndpoint, 22);
- session.setPassword(new String(dlg.passwdEntry.getPassword()));
- session.connect();
-
- String[] tokens = cmd.split("\\s");
- for (int i = 0; i < tokens.length; i++) {
- if (tokens[i].equals("-L")) {
- String[] par = tokens[++i].split(":");
- int localPort = Integer.parseInt(par[0].trim());
- String remoteHost = par[1].trim();
- int remotePort = Integer.parseInt(par[2].trim());
- session.setPortForwardingL(localPort, remoteHost, remotePort);
- } else if (tokens[i].equals("-R")) {
- String[] par = tokens[++i].split(":");
- int remotePort = Integer.parseInt(par[0].trim());
- String localHost = par[1].trim();
- int localPort = Integer.parseInt(par[2].trim());
- session.setPortForwardingR(remotePort, localHost, localPort);
- }
- }
- } catch (java.lang.Exception e) {
- System.out.println(" Tunneling command failed: "+e.toString());
- return false;
- }
- return true;
- }
-
- static LogWriter vlog = new LogWriter("tunnel");
-}