blob: a66d83ae7d0a302bd4e1cb95c5f0231b19d9ee25 [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
40public class tunnel
41{
42 private final static Integer SERVER_PORT_OFFSET = 5900;;
43 private final static String DEFAULT_SSH_CMD = "/usr/bin/ssh";
44 private final static String DEFAULT_TUNNEL_CMD
45 = DEFAULT_SSH_CMD+" -f -L %L:localhost:%R %H sleep 20";
46 private final static String DEFAULT_VIA_CMD
47 = 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;
53
54 /* True if there was -tunnel or -via option in the command line. */
55 private static boolean tunnelSpecified = false;
56
57 /* True if it was -tunnel, not -via option. */
58 private static boolean tunnelOption = false;
59
60 /* "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;
65
66 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");
77
78 tunnelSpecified = true;
79 if (argv[tunnelArgIndex].equalsIgnoreCase("-tunnel"))
80 tunnelOption = true;
81
82 pattern = getCmdPattern();
83 if (pattern == null)
84 return false;
85
86 localPort[0] = TcpSocket.findFreeTcpPort();
87 if (localPort[0] == 0)
88 return false;
89
90 if (tunnelOption) {
91 processTunnelArgs(remoteHost, remotePort, localPort,
92 pargc, argv, tunnelArgIndex);
93 } else {
94 processViaArgs(gatewayHost, remoteHost, remotePort, localPort,
95 pargc, argv, tunnelArgIndex);
96 }
97
98 localPortStr = Integer.toString(localPort[0]).toCharArray();
99 remotePortStr = Integer.toString(remotePort[0]).toCharArray();
100
101 if (!fillCmdPattern(cmd, pattern, gatewayHost.toString().toCharArray(),
102 remoteHost.toString().toCharArray(), remotePortStr, localPortStr))
103 return false;
104
105 if (!runCommand(new String(cmd)))
106 return false;
107
108 return true;
109 }
110
111 private static void
112 processTunnelArgs(StringBuilder remoteHost, int[] remotePort,
113 int[] localPort, int pargc, String[] argv,
114 int tunnelArgIndex)
115 {
116 String pdisplay;
117
118 if (tunnelArgIndex >= pargc - 1)
119 VncViewer.usage();
120
121 pdisplay = argv[pargc - 1].split(":")[1];
122 if (pdisplay == null || pdisplay == argv[pargc - 1])
123 VncViewer.usage();
124
125 if (pdisplay.matches("/[^0-9]/"))
126 VncViewer.usage();
127
128 remotePort[0] = Integer.parseInt(pdisplay);
129 if (remotePort[0] < 100)
130 remotePort[0] = remotePort[0] + SERVER_PORT_OFFSET;
131
132 lastArgv = new String("localhost::"+localPort[0]);
133
134 remoteHost.setLength(0);
135 remoteHost.insert(0, argv[pargc - 1].split(":")[0]);
136 argv[pargc - 1] = lastArgv;
137
138 //removeArgs(pargc, argv, tunnelArgIndex, 1);
139 }
140
141 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;
149
150 if (tunnelArgIndex >= pargc - 2)
151 VncViewer.usage();
152
153 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 }
174
175 lastArgv = "localhost::"+localPort[0];
176
177 gatewayHost.setLength(0);
178 gatewayHost.insert(0, argv[tunnelArgIndex + 1]);
179
180 if (!argv[pargc - 1].split(":", 2)[0].equals("")) {
181 remoteHost.setLength(0);
182 remoteHost.insert(0, argv[pargc - 1].split(":", 2)[0]);
183 }
184
185 argv[pargc - 1] = lastArgv;
186
187 //removeArgs(pargc, argv, tunnelArgIndex, 2);
188 }
189
190 private static char[]
191 getCmdPattern()
192 {
193 String pattern = "";
194
195 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 }
201 } catch (java.lang.Exception e) {
202 vlog.info(e.toString());
203 }
204 if (pattern == null || pattern.equals(""))
205 pattern = (tunnelOption) ? DEFAULT_TUNNEL_CMD : DEFAULT_VIA_CMD;
206
207 return pattern.toCharArray();
208 }
209
210 /* Note: in fillCmdPattern() result points to a 1024-byte buffer */
211
212 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 }
252
253 if (pattern.length > 1024) {
254 vlog.error("Tunneling command is too long.");
255 return false;
256 }
257
258 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 }
266
267 return true;
268 }
269
270 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 }
325
326 static LogWriter vlog = new LogWriter("tunnel");
327}