blob: 6d55fece567b303f0dd900c5ac3d19886ee84bd8 [file] [log] [blame]
/*
* 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");
}