Skip to content

Commit

Permalink
Java: Fix issues with external SSH feature
Browse files Browse the repository at this point in the history
- Windows: ExtSSH now works properly with interactive SSH authentication
  methods.
- All: ExtSSH now throws an exception if the SSH subprocess fails to
  launch for any reason.
- All: ExtSSH now redirects input and output from the SSH subprocess to
  the console that launched the viewer.
  • Loading branch information
dcommander committed Mar 18, 2020
1 parent 10e5aff commit 125132d
Show file tree
Hide file tree
Showing 5 changed files with 239 additions and 28 deletions.
7 changes: 7 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ box.
full-screen mode setting did not take effect if it was changed in the Default
Options dialog.

6. The external SSH feature in the Java TurboVNC Viewer now redirects input and
output from the SSH subprocess to the console that launched the viewer. In
addition to allowing external SSH issues to be diagnosed more easily, this also
allows interactive SSH authentication methods to be used on Windows.
Furthermore, the Java TurboVNC Viewer now throws an error if the SSH subprocess
fails to launch for any reason.


2.2.4
=====
Expand Down
9 changes: 4 additions & 5 deletions doc/config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ compare full rectangles, as TurboVNC 1.2.x did. |
beginning with the "%" character, and uses the resulting command line to
establish the secure tunnel to the VNC gateway. If ''VNC_VIA_CMD'' is not
set, then this command line defaults to
''/usr/bin/ssh -f -L %L:%H:%R %G sleep 20'' (or
''ssh.exe -f -L %L:%H:%R %G sleep 20'' if using the Windows native viewer.)
''/usr/bin/ssh -f -L %L:%H:%R %G sleep 20'' on Linux/Un*x and Mac systems and
''ssh.exe -f -L %L:%H:%R %G sleep 20'' on Windows systems.
{nl}{nl}
When the ''-tunnel'' option (or the ''tunnel'' parameter in the Java viewer,
along with the ''extssh'' parameter) is used, the TurboVNC Viewer reads the
Expand All @@ -158,9 +158,8 @@ compare full rectangles, as TurboVNC 1.2.x did. |
beginning with the "%" character, and uses the resulting command line to
establish the secure tunnel to the VNC host. If ''VNC_TUNNEL_CMD'' is not
set, then this command line defaults to
''/usr/bin/ssh -f -L %L:localhost:%R %H sleep 20'' (or
''ssh.exe -f -L %L:localhost:%R %H sleep 20'' if using the Windows native
viewer.)
''/usr/bin/ssh -f -L %L:localhost:%R %H sleep 20'' on Linux/Un*x and Mac
systems and ''ssh.exe -f -L %L:localhost:%R %H sleep 20'' on Windows systems.
{nl}{nl}
The following patterns are recognized in the ''VNC_VIA_CMD'' and
''VNC_TUNNEL_CMD'' environment variables (note that ''%H'', ''%L'' and ''%R''
Expand Down
37 changes: 18 additions & 19 deletions doc/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
<meta name="language" content="en">
<meta name="date" content="2020-02-26T23:56:33">
<meta name="date" content="2020-03-18T16:52:57">
<meta name="generator" content="deplate.rb 0.8.5">
<title>User&rsquo;s Guide for TurboVNC 2.2.5</title>
<link rel="start" href="index.html" title="Frontpage">
Expand Down Expand Up @@ -2953,27 +2953,26 @@ <h2 id="hd0011002">11.2&nbsp;Viewer Settings</h2>
to establish the secure tunnel to the VNC gateway. If
<code>VNC_VIA_CMD</code> is not set, then this command line defaults to
<code>/usr/bin/ssh&nbsp;-f&nbsp;-L&nbsp;%L:%H:%R&nbsp;%G&nbsp;sleep&nbsp;20</code>
(or
on Linux/Un*x and Mac systems and
<code>ssh.exe&nbsp;-f&nbsp;-L&nbsp;%L:%H:%R&nbsp;%G&nbsp;sleep&nbsp;20</code>
if using the Windows native viewer.) <br /><br /> When the
<code>-tunnel</code> option (or the <code>tunnel</code> parameter in the
Java viewer, along with the <code>extssh</code> parameter) is used, the
TurboVNC Viewer reads the <code>VNC_TUNNEL_CMD</code> environment
variable (the Java viewer can also read this information from the
<code>turbovnc.tunnel</code> system property), expands patterns
beginning with the &ldquo;%&rdquo; character, and uses the resulting
command line to establish the secure tunnel to the VNC host. If
<code>VNC_TUNNEL_CMD</code> is not set, then this command line defaults
to
on Windows systems. <br /><br /> When the <code>-tunnel</code> option
(or the <code>tunnel</code> parameter in the Java viewer, along with the
<code>extssh</code> parameter) is used, the TurboVNC Viewer reads the
<code>VNC_TUNNEL_CMD</code> environment variable (the Java viewer can
also read this information from the <code>turbovnc.tunnel</code> system
property), expands patterns beginning with the &ldquo;%&rdquo;
character, and uses the resulting command line to establish the secure
tunnel to the VNC host. If <code>VNC_TUNNEL_CMD</code> is not set, then
this command line defaults to
<code>/usr/bin/ssh&nbsp;-f&nbsp;-L&nbsp;%L:localhost:%R&nbsp;%H&nbsp;sleep&nbsp;20</code>
(or
on Linux/Un*x and Mac systems and
<code>ssh.exe&nbsp;-f&nbsp;-L&nbsp;%L:localhost:%R&nbsp;%H&nbsp;sleep&nbsp;20</code>
if using the Windows native viewer.) <br /><br /> The following patterns
are recognized in the <code>VNC_VIA_CMD</code> and
<code>VNC_TUNNEL_CMD</code> environment variables (note that
<code>%H</code>, <code>%L</code> and <code>%R</code> must be present in
the command template, and <code>%G</code> must also be present if using
the <code>-via</code> option): <br /><br />
on Windows systems. <br /><br /> The following patterns are recognized
in the <code>VNC_VIA_CMD</code> and <code>VNC_TUNNEL_CMD</code>
environment variables (note that <code>%H</code>, <code>%L</code> and
<code>%R</code> must be present in the command template, and
<code>%G</code> must also be present if using the <code>-via</code>
option): <br /><br />
<div class="table">
<table class="standard">
<tr class="standard">
Expand Down
200 changes: 200 additions & 0 deletions java/com/turbovnc/vncviewer/ArgumentTokenizer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/* Copyright (c) 2001-2010, JavaPLT group at Rice University ([email protected])
* Copyright (c) 2020, D. R. Commander
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This file was part of DrJava. Download the current version of this project
* from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
*/

package com.turbovnc.vncviewer;

import java.util.List;
import java.util.LinkedList;

/**
* Utility class which can tokenize a String into a list of String arguments,
* with behavior similar to parsing command line arguments to a program.
* Quoted Strings are treated as single arguments, and escaped characters
* are translated so that the tokenized arguments have the same meaning.
* Since all methods are static, the class is declared abstract to prevent
* instantiation.
* @version $Id$
*/
public abstract class ArgumentTokenizer {
private static final int NO_TOKEN_STATE = 0;
private static final int NORMAL_TOKEN_STATE = 1;
private static final int SINGLE_QUOTE_STATE = 2;
private static final int DOUBLE_QUOTE_STATE = 3;

/** Tokenizes the given String into String tokens
* @param arguments A String containing one or more command-line style
* arguments to be tokenized.
* @return A list of parsed and properly escaped arguments.
*/
public static List<String> tokenize(String arguments) {
return tokenize(arguments, false);
}

/** Tokenizes the given String into String tokens.
* @param arguments A String containing one or more command-line style
* arguments to be tokenized.
* @param stringify whether or not to include escape special characters
* @return A list of parsed and properly escaped arguments.
*/
public static List<String> tokenize(String arguments, boolean stringify) {

LinkedList<String> argList = new LinkedList<String>();
StringBuilder currArg = new StringBuilder();
boolean escaped = false;
int state = NO_TOKEN_STATE; // start in the NO_TOKEN_STATE
int len = arguments.length();

// Loop over each character in the string
for (int i = 0; i < len; i++) {
char c = arguments.charAt(i);
if (escaped) {
// Escaped state: just append the next character to the current arg.
escaped = false;
currArg.append(c);
} else {
switch (state) {
case SINGLE_QUOTE_STATE:
if (c == '\'') {
// Seen the close quote; continue this arg until whitespace is
// seen
state = NORMAL_TOKEN_STATE;
} else {
currArg.append(c);
}
break;
case DOUBLE_QUOTE_STATE:
if (c == '"') {
// Seen the close quote; continue this arg until whitespace is
// seen
state = NORMAL_TOKEN_STATE;
} else if (c == '\\' && !VncViewer.OS.startsWith("windows")) {
// Look ahead, and only escape quotes or backslashes
i++;
char next = arguments.charAt(i);
if (next == '"' || next == '\\') {
currArg.append(next);
} else {
currArg.append(c);
currArg.append(next);
}
} else {
currArg.append(c);
}
break;
case NO_TOKEN_STATE:
case NORMAL_TOKEN_STATE:
switch (c) {
case '\\':
if (VncViewer.OS.startsWith("windows"))
currArg.append(c);
else
escaped = true;
state = NORMAL_TOKEN_STATE;
break;
case '\'':
state = SINGLE_QUOTE_STATE;
break;
case '"':
state = DOUBLE_QUOTE_STATE;
break;
default:
if (!Character.isWhitespace(c)) {
currArg.append(c);
state = NORMAL_TOKEN_STATE;
} else if (state == NORMAL_TOKEN_STATE) {
// Whitespace ends the token; start a new one
argList.add(currArg.toString());
currArg = new StringBuilder();
state = NO_TOKEN_STATE;
}
}
break;
default:
throw new IllegalStateException("ArgumentTokenizer state " +
state + " is invalid!");
}
}
}

// If we're still escaped, put in the backslash
if (escaped) {
currArg.append('\\');
argList.add(currArg.toString());
// Close the last argument if we haven't yet
} else if (state != NO_TOKEN_STATE) {
argList.add(currArg.toString());
}
// Format each argument if we've been told to stringify them
if (stringify) {
for (int i = 0; i < argList.size(); i++) {
argList.set(i, "\"" + escapeQuotesAndBackslashes(argList.get(i)) +
"\"");
}
}
return argList;
}

/** Inserts backslashes before any occurrences of a backslash or
* quote in the given string. Also converts any special characters
* appropriately.
*/
protected static String escapeQuotesAndBackslashes(String s) {
final StringBuilder buf = new StringBuilder(s);

// Walk backwards, looking for quotes or backslashes.
// If we see any, insert an extra backslash into the buffer at
// the same index. (By walking backwards, the index into the buffer
// will remain correct as we change the buffer.)
for (int i = s.length() - 1; i >= 0; i--) {
char c = s.charAt(i);
if ((c == '\\') || (c == '"')) {
buf.insert(i, '\\');
// Replace any special characters with escaped versions
} else if (c == '\n') {
buf.deleteCharAt(i);
buf.insert(i, "\\n");
} else if (c == '\t') {
buf.deleteCharAt(i);
buf.insert(i, "\\t");
} else if (c == '\r') {
buf.deleteCharAt(i);
buf.insert(i, "\\r");
} else if (c == '\b') {
buf.deleteCharAt(i);
buf.insert(i, "\\b");
} else if (c == '\f') {
buf.deleteCharAt(i);
buf.insert(i, "\\f");
}
}
return buf.toString();
}
}
14 changes: 10 additions & 4 deletions java/com/turbovnc/vncviewer/Tunnel.java
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ private static void createTunnelJSch(String host, Options opts)
VNC_TUNNEL_CMD and VNC_VIA_CMD environment variables as the native viewers
do. */

private static final String DEFAULT_SSH_CMD = "/usr/bin/ssh";
private static final String DEFAULT_SSH_CMD =
(VncViewer.OS.startsWith("windows") ? "ssh.exe" : "/usr/bin/ssh");
private static final String DEFAULT_TUNNEL_CMD =
DEFAULT_SSH_CMD + " -f -L %L:localhost:%R %H sleep 20";
private static final String DEFAULT_VIA_CMD =
Expand All @@ -206,9 +207,14 @@ public static void createTunnelExt(String gatewayHost, String remoteHost,
remotePort, localPort, opts);

vlog.debug("SSH command line: " + command);
Process p = Runtime.getRuntime().exec(command);
if (p != null)
p.waitFor();
List<String> args = ArgumentTokenizer.tokenize(command);
ProcessBuilder pb = new ProcessBuilder(args);
pb.redirectInput(ProcessBuilder.Redirect.INHERIT);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
Process p = pb.start();
if (p == null || p.waitFor() != 0)
throw new ErrorException("External SSH error");
}

private static String fillCmdPattern(String pattern, String gatewayHost,
Expand Down

0 comments on commit 125132d

Please sign in to comment.