blob: 6d55fece567b303f0dd900c5ac3d19886ee84bd8 [file] [log] [blame]
Brian Hinz94653cf2012-05-06 19:18:05 +00001/*
2 * Copyright (C) 2012 Brian P. Hinz. All Rights Reserved.
3 * Copyright (C) 2000 Const Kaplinsky. All Rights Reserved.
4 * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
5 *
6 * This is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This software is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this software; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
19 * USA.
20 */
21
22/*
23 * tunnel.java - SSH tunneling support
24 */
25
26package com.tigervnc.vncviewer;
27
28import java.io.File;
29import java.lang.Character;
30import java.util.ArrayList;
31import java.util.Iterator;
32
33import com.tigervnc.rdr.*;
34import com.tigervnc.rfb.*;
35import com.tigervnc.network.*;
36
37import com.jcraft.jsch.JSch;
38import com.jcraft.jsch.Session;
39
Brian Hinzc0a36092013-05-12 15:46:09 +000040public class tunnel
Brian Hinz94653cf2012-05-06 19:18:05 +000041{
42 private final static Integer SERVER_PORT_OFFSET = 5900;;
43 private final static String DEFAULT_SSH_CMD = "/usr/bin/ssh";
Brian Hinzc0a36092013-05-12 15:46:09 +000044 private final static String DEFAULT_TUNNEL_CMD
Brian Hinz94653cf2012-05-06 19:18:05 +000045 = DEFAULT_SSH_CMD+" -f -L %L:localhost:%R %H sleep 20";
Brian Hinzc0a36092013-05-12 15:46:09 +000046 private final static String DEFAULT_VIA_CMD
Brian Hinz94653cf2012-05-06 19:18:05 +000047 = DEFAULT_SSH_CMD+" -f -L %L:%H:%R %G sleep 20";
48
49 private final static int H = 17;
50 private final static int G = 16;
51 private final static int R = 27;
52 private final static int L = 21;
Brian Hinzc0a36092013-05-12 15:46:09 +000053
Brian Hinz94653cf2012-05-06 19:18:05 +000054 /* True if there was -tunnel or -via option in the command line. */
55 private static boolean tunnelSpecified = false;
Brian Hinzc0a36092013-05-12 15:46:09 +000056
Brian Hinz94653cf2012-05-06 19:18:05 +000057 /* True if it was -tunnel, not -via option. */
58 private static boolean tunnelOption = false;
Brian Hinzc0a36092013-05-12 15:46:09 +000059
Brian Hinz94653cf2012-05-06 19:18:05 +000060 /* "Hostname:display" pair in the command line will be substituted
61 by this fake argument when tunneling is used. */
62 private static String lastArgv;
63
64 private static String tunnelEndpoint;
Brian Hinzc0a36092013-05-12 15:46:09 +000065
Brian Hinz94653cf2012-05-06 19:18:05 +000066 public static Boolean
67 createTunnel(int pargc, String[] argv, int tunnelArgIndex)
68 {
69 char[] pattern;
70 char[] cmd = new char[1024];
71 int[] localPort = new int[1];
72 int[] remotePort = new int[1];
73 char[] localPortStr = new char[8];
74 char[] remotePortStr = new char[8];
75 StringBuilder gatewayHost = new StringBuilder("");
76 StringBuilder remoteHost = new StringBuilder("localhost");
Brian Hinzc0a36092013-05-12 15:46:09 +000077
Brian Hinz94653cf2012-05-06 19:18:05 +000078 tunnelSpecified = true;
79 if (argv[tunnelArgIndex].equalsIgnoreCase("-tunnel"))
80 tunnelOption = true;
Brian Hinzc0a36092013-05-12 15:46:09 +000081
Brian Hinz94653cf2012-05-06 19:18:05 +000082 pattern = getCmdPattern();
83 if (pattern == null)
84 return false;
Brian Hinzc0a36092013-05-12 15:46:09 +000085
Brian Hinz94653cf2012-05-06 19:18:05 +000086 localPort[0] = TcpSocket.findFreeTcpPort();
87 if (localPort[0] == 0)
88 return false;
Brian Hinzc0a36092013-05-12 15:46:09 +000089
Brian Hinz94653cf2012-05-06 19:18:05 +000090 if (tunnelOption) {
91 processTunnelArgs(remoteHost, remotePort, localPort,
92 pargc, argv, tunnelArgIndex);
93 } else {
94 processViaArgs(gatewayHost, remoteHost, remotePort, localPort,
95 pargc, argv, tunnelArgIndex);
96 }
Brian Hinzc0a36092013-05-12 15:46:09 +000097
Brian Hinz94653cf2012-05-06 19:18:05 +000098 localPortStr = Integer.toString(localPort[0]).toCharArray();
99 remotePortStr = Integer.toString(remotePort[0]).toCharArray();
Brian Hinzc0a36092013-05-12 15:46:09 +0000100
101 if (!fillCmdPattern(cmd, pattern, gatewayHost.toString().toCharArray(),
Brian Hinz94653cf2012-05-06 19:18:05 +0000102 remoteHost.toString().toCharArray(), remotePortStr, localPortStr))
103 return false;
Brian Hinzc0a36092013-05-12 15:46:09 +0000104
Brian Hinz94653cf2012-05-06 19:18:05 +0000105 if (!runCommand(new String(cmd)))
106 return false;
Brian Hinzc0a36092013-05-12 15:46:09 +0000107
Brian Hinz94653cf2012-05-06 19:18:05 +0000108 return true;
109 }
Brian Hinzc0a36092013-05-12 15:46:09 +0000110
Brian Hinz94653cf2012-05-06 19:18:05 +0000111 private static void
Brian Hinzc0a36092013-05-12 15:46:09 +0000112 processTunnelArgs(StringBuilder remoteHost, int[] remotePort,
113 int[] localPort, int pargc, String[] argv,
Brian Hinz94653cf2012-05-06 19:18:05 +0000114 int tunnelArgIndex)
115 {
116 String pdisplay;
Brian Hinzc0a36092013-05-12 15:46:09 +0000117
Brian Hinz94653cf2012-05-06 19:18:05 +0000118 if (tunnelArgIndex >= pargc - 1)
119 VncViewer.usage();
Brian Hinzc0a36092013-05-12 15:46:09 +0000120
Brian Hinz94653cf2012-05-06 19:18:05 +0000121 pdisplay = argv[pargc - 1].split(":")[1];
122 if (pdisplay == null || pdisplay == argv[pargc - 1])
123 VncViewer.usage();
Brian Hinzc0a36092013-05-12 15:46:09 +0000124
Brian Hinz94653cf2012-05-06 19:18:05 +0000125 if (pdisplay.matches("/[^0-9]/"))
126 VncViewer.usage();
Brian Hinzc0a36092013-05-12 15:46:09 +0000127
Brian Hinz94653cf2012-05-06 19:18:05 +0000128 remotePort[0] = Integer.parseInt(pdisplay);
129 if (remotePort[0] < 100)
130 remotePort[0] = remotePort[0] + SERVER_PORT_OFFSET;
Brian Hinzc0a36092013-05-12 15:46:09 +0000131
Brian Hinz94653cf2012-05-06 19:18:05 +0000132 lastArgv = new String("localhost::"+localPort[0]);
Brian Hinzc0a36092013-05-12 15:46:09 +0000133
Brian Hinz94653cf2012-05-06 19:18:05 +0000134 remoteHost.setLength(0);
135 remoteHost.insert(0, argv[pargc - 1].split(":")[0]);
136 argv[pargc - 1] = lastArgv;
Brian Hinzc0a36092013-05-12 15:46:09 +0000137
Brian Hinz94653cf2012-05-06 19:18:05 +0000138 //removeArgs(pargc, argv, tunnelArgIndex, 1);
139 }
Brian Hinzc0a36092013-05-12 15:46:09 +0000140
Brian Hinz94653cf2012-05-06 19:18:05 +0000141 private static void
142 processViaArgs(StringBuilder gatewayHost, StringBuilder remoteHost,
143 int[] remotePort, int[] localPort,
144 int pargc, String[] argv, int tunnelArgIndex)
145 {
146 String colonPos;
147 int len, portOffset;
148 int disp;
Brian Hinzc0a36092013-05-12 15:46:09 +0000149
Brian Hinz94653cf2012-05-06 19:18:05 +0000150 if (tunnelArgIndex >= pargc - 2)
151 VncViewer.usage();
Brian Hinzc0a36092013-05-12 15:46:09 +0000152
Brian Hinz94653cf2012-05-06 19:18:05 +0000153 colonPos = argv[pargc - 1].split(":", 2)[1];
154 if (colonPos == null) {
155 /* No colon -- use default port number */
156 remotePort[0] = SERVER_PORT_OFFSET;
157 } else {
158 len = colonPos.length();
159 portOffset = SERVER_PORT_OFFSET;
160 if (colonPos.startsWith(":")) {
161 /* Two colons -- interpret as a port number */
162 colonPos.replaceFirst(":", "");
163 len--;
164 portOffset = 0;
165 }
166 if (len == 0 || colonPos.matches("/[^0-9]/")) {
167 VncViewer.usage();
168 }
169 disp = Integer.parseInt(colonPos);
170 if (portOffset != 0 && disp >= 100)
171 portOffset = 0;
172 remotePort[0] = disp + portOffset;
173 }
Brian Hinzc0a36092013-05-12 15:46:09 +0000174
Brian Hinz94653cf2012-05-06 19:18:05 +0000175 lastArgv = "localhost::"+localPort[0];
Brian Hinzc0a36092013-05-12 15:46:09 +0000176
Brian Hinz94653cf2012-05-06 19:18:05 +0000177 gatewayHost.setLength(0);
178 gatewayHost.insert(0, argv[tunnelArgIndex + 1]);
Brian Hinzc0a36092013-05-12 15:46:09 +0000179
Brian Hinz94653cf2012-05-06 19:18:05 +0000180 if (!argv[pargc - 1].split(":", 2)[0].equals("")) {
181 remoteHost.setLength(0);
182 remoteHost.insert(0, argv[pargc - 1].split(":", 2)[0]);
183 }
Brian Hinzc0a36092013-05-12 15:46:09 +0000184
Brian Hinz94653cf2012-05-06 19:18:05 +0000185 argv[pargc - 1] = lastArgv;
Brian Hinzc0a36092013-05-12 15:46:09 +0000186
Brian Hinz94653cf2012-05-06 19:18:05 +0000187 //removeArgs(pargc, argv, tunnelArgIndex, 2);
188 }
Brian Hinzc0a36092013-05-12 15:46:09 +0000189
Brian Hinz94653cf2012-05-06 19:18:05 +0000190 private static char[]
191 getCmdPattern()
192 {
193 String pattern = "";
Brian Hinzc0a36092013-05-12 15:46:09 +0000194
Brian Hinz94653cf2012-05-06 19:18:05 +0000195 try {
196 if (tunnelOption) {
Brian Hinzfd9419b2012-05-23 03:40:07 +0000197 pattern = System.getProperty("VNC_TUNNEL_CMD");
Brian Hinz94653cf2012-05-06 19:18:05 +0000198 } else {
Brian Hinzfd9419b2012-05-23 03:40:07 +0000199 pattern = System.getProperty("VNC_VIA_CMD");
Brian Hinz94653cf2012-05-06 19:18:05 +0000200 }
Brian Hinzc0a36092013-05-12 15:46:09 +0000201 } catch (java.lang.Exception e) {
Brian Hinz94653cf2012-05-06 19:18:05 +0000202 vlog.info(e.toString());
203 }
204 if (pattern == null || pattern.equals(""))
205 pattern = (tunnelOption) ? DEFAULT_TUNNEL_CMD : DEFAULT_VIA_CMD;
Brian Hinzc0a36092013-05-12 15:46:09 +0000206
Brian Hinz94653cf2012-05-06 19:18:05 +0000207 return pattern.toCharArray();
208 }
Brian Hinzc0a36092013-05-12 15:46:09 +0000209
Brian Hinz94653cf2012-05-06 19:18:05 +0000210 /* Note: in fillCmdPattern() result points to a 1024-byte buffer */
Brian Hinzc0a36092013-05-12 15:46:09 +0000211
Brian Hinz94653cf2012-05-06 19:18:05 +0000212 private static boolean
213 fillCmdPattern(char[] result, char[] pattern,
214 char[] gatewayHost, char[] remoteHost,
215 char[] remotePort, char[] localPort)
216 {
217 int i, j;
218 boolean H_found = false, G_found = false, R_found = false, L_found = false;
219
220 for (i=0, j=0; i < pattern.length && j<1023; i++, j++) {
221 if (pattern[i] == '%') {
222 switch (pattern[++i]) {
223 case 'H':
224 System.arraycopy(remoteHost, 0, result, j, remoteHost.length);
225 j += remoteHost.length;
226 H_found = true;
227 tunnelEndpoint = new String(remoteHost);
228 continue;
229 case 'G':
230 System.arraycopy(gatewayHost, 0, result, j, gatewayHost.length);
231 j += gatewayHost.length;
232 G_found = true;
233 tunnelEndpoint = new String(gatewayHost);
234 continue;
235 case 'R':
236 System.arraycopy(remotePort, 0, result, j, remotePort.length);
237 j += remotePort.length;
238 R_found = true;
239 continue;
240 case 'L':
241 System.arraycopy(localPort, 0, result, j, localPort.length);
242 j += localPort.length;
243 L_found = true;
244 continue;
245 case '\0':
246 i--;
247 continue;
248 }
249 }
250 result[j] = pattern[i];
251 }
Brian Hinzc0a36092013-05-12 15:46:09 +0000252
Brian Hinz94653cf2012-05-06 19:18:05 +0000253 if (pattern.length > 1024) {
254 vlog.error("Tunneling command is too long.");
255 return false;
256 }
Brian Hinzc0a36092013-05-12 15:46:09 +0000257
Brian Hinz94653cf2012-05-06 19:18:05 +0000258 if (!H_found || !R_found || !L_found) {
259 vlog.error("%H, %R or %L absent in tunneling command.");
260 return false;
261 }
262 if (!tunnelOption && !G_found) {
263 vlog.error("%G pattern absent in tunneling command.");
264 return false;
265 }
Brian Hinzc0a36092013-05-12 15:46:09 +0000266
Brian Hinz94653cf2012-05-06 19:18:05 +0000267 return true;
268 }
Brian Hinzc0a36092013-05-12 15:46:09 +0000269
Brian Hinz94653cf2012-05-06 19:18:05 +0000270 private static Boolean
271 runCommand(String cmd)
272 {
273 try{
274 JSch jsch=new JSch();
275 String homeDir = new String("");
276 try {
277 homeDir = System.getProperty("user.home");
278 } catch(java.security.AccessControlException e) {
279 System.out.println("Cannot access user.home system property");
280 }
281 // NOTE: jsch does not support all ciphers. User may be
282 // prompted to accept host key authenticy even if
283 // the key is in the known_hosts file.
284 File knownHosts = new File(homeDir+"/.ssh/known_hosts");
285 if (knownHosts.exists() && knownHosts.canRead())
286 jsch.setKnownHosts(knownHosts.getAbsolutePath());
287 ArrayList<File> privateKeys = new ArrayList<File>();
288 privateKeys.add(new File(homeDir+"/.ssh/id_rsa"));
289 privateKeys.add(new File(homeDir+"/.ssh/id_dsa"));
Brian Hinzd93a26d2012-12-14 22:40:02 +0000290 for (Iterator<File> i = privateKeys.iterator(); i.hasNext();) {
Brian Hinz94653cf2012-05-06 19:18:05 +0000291 File privateKey = (File)i.next();
292 if (privateKey.exists() && privateKey.canRead())
293 jsch.addIdentity(privateKey.getAbsolutePath());
294 }
295 // username and passphrase will be given via UserInfo interface.
296 PasswdDialog dlg = new PasswdDialog(new String("SSH Authentication"), false, false);
Brian Hinzfd9419b2012-05-23 03:40:07 +0000297 dlg.promptPassword(new String("SSH Authentication"));
Brian Hinz94653cf2012-05-06 19:18:05 +0000298
299 Session session=jsch.getSession(dlg.userEntry.getText(), tunnelEndpoint, 22);
Brian Hinzfd9419b2012-05-23 03:40:07 +0000300 session.setPassword(new String(dlg.passwdEntry.getPassword()));
Brian Hinz94653cf2012-05-06 19:18:05 +0000301 session.connect();
302
303 String[] tokens = cmd.split("\\s");
304 for (int i = 0; i < tokens.length; i++) {
305 if (tokens[i].equals("-L")) {
306 String[] par = tokens[++i].split(":");
307 int localPort = Integer.parseInt(par[0].trim());
308 String remoteHost = par[1].trim();
309 int remotePort = Integer.parseInt(par[2].trim());
310 session.setPortForwardingL(localPort, remoteHost, remotePort);
311 } else if (tokens[i].equals("-R")) {
312 String[] par = tokens[++i].split(":");
313 int remotePort = Integer.parseInt(par[0].trim());
314 String localHost = par[1].trim();
315 int localPort = Integer.parseInt(par[2].trim());
316 session.setPortForwardingR(remotePort, localHost, localPort);
317 }
318 }
319 } catch (java.lang.Exception e) {
320 System.out.println(" Tunneling command failed: "+e.toString());
321 return false;
322 }
323 return true;
324 }
Brian Hinzc0a36092013-05-12 15:46:09 +0000325
Brian Hinz94653cf2012-05-06 19:18:05 +0000326 static LogWriter vlog = new LogWriter("tunnel");
327}