1 | /******************************************************************************
|
---|
2 | *
|
---|
3 | * Copyright (c) 1998,99 by Mindbright Technology AB, Stockholm, Sweden.
|
---|
4 | * www.mindbright.se, [email protected]
|
---|
5 | *
|
---|
6 | * This program 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 program 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 | *****************************************************************************
|
---|
17 | * $Author: mats $
|
---|
18 | * $Date: 2000/04/04 09:34:12 $
|
---|
19 | * $Name: rel1-2-1 $
|
---|
20 | *****************************************************************************/
|
---|
21 | package mindbright.ssh;
|
---|
22 |
|
---|
23 | import java.net.*;
|
---|
24 | import java.io.*;
|
---|
25 | import java.math.BigInteger;
|
---|
26 | import java.util.Vector;
|
---|
27 |
|
---|
28 | import mindbright.security.*;
|
---|
29 | import mindbright.terminal.*;
|
---|
30 |
|
---|
31 | /**
|
---|
32 | * This class contains the main functionality for setting up a connection to a
|
---|
33 | * ssh-server. It can be used both to implement a "full" ssh-client, or it can
|
---|
34 | * be used to fire off a singe command on the server (both in a background
|
---|
35 | * thread and in the current-/foreground-thread). A set of properties can be
|
---|
36 | * used to control different aspects of the connection. These are fetched from
|
---|
37 | * an object implementing the <code>SSHClientUser</code>-interface. The
|
---|
38 | * authentication can be done in different ways, all which is handled through an
|
---|
39 | * object implementing the <code>SSHAuthenticator</code>-interface. The
|
---|
40 | * console-output of the <code>SSHClient</code> is (optionally) handled through
|
---|
41 | * an object implementing the <code>SSHConsole</code>-interface. <p>
|
---|
42 | *
|
---|
43 | * A class realizing a full interactive ssh-client is
|
---|
44 | * <code>SSHInteractiveClient</code>. The <code>SSHClient</code>-class
|
---|
45 | * is also used transparently from the <code>SSHSocket</code>- and <code>SSHServerSocket</code>-
|
---|
46 | * classes (through the <code>SSHSocketFactory</code>- and <code>SSHSocketImpl</code>-classes).
|
---|
47 | *
|
---|
48 | * @author Mats Andersson
|
---|
49 | * @version 0.96, 26/11/98
|
---|
50 | * @see SSHAuthenticator
|
---|
51 | * @see SSHClientUser
|
---|
52 | * @see SSHConsole
|
---|
53 | * @see SSHSocketFactory
|
---|
54 | * @see SSHSocketImpl */
|
---|
55 | public class SSHClient extends SSH {
|
---|
56 |
|
---|
57 | static public class AuthFailException extends IOException {
|
---|
58 | public AuthFailException(String msg) {
|
---|
59 | super(msg);
|
---|
60 | }
|
---|
61 | }
|
---|
62 |
|
---|
63 | static public class ExitMonitor implements Runnable {
|
---|
64 | SSHClient client;
|
---|
65 | long msTimeout;
|
---|
66 | public ExitMonitor(SSHClient client, long msTimeout) {
|
---|
67 | this.msTimeout = msTimeout;
|
---|
68 | this.client = client;
|
---|
69 | }
|
---|
70 | public ExitMonitor(SSHClient client) {
|
---|
71 | this(client, 0);
|
---|
72 | }
|
---|
73 | public void run() {
|
---|
74 | client.waitForExit(msTimeout);
|
---|
75 | // If we have allready exited gracefully don't report...
|
---|
76 | //
|
---|
77 | if(!client.gracefulExit)
|
---|
78 | client.disconnect(false);
|
---|
79 | }
|
---|
80 | }
|
---|
81 |
|
---|
82 | private class KeepAliveThread extends Thread {
|
---|
83 | int interval;
|
---|
84 | public KeepAliveThread(int i) {
|
---|
85 | super();
|
---|
86 | interval = i;
|
---|
87 | }
|
---|
88 | public synchronized void setInterval(int i) {
|
---|
89 | interval = i;
|
---|
90 | }
|
---|
91 | public void run() {
|
---|
92 | int i;
|
---|
93 | SSHPduOutputStream ignmsg;
|
---|
94 | while(true) {
|
---|
95 | try {
|
---|
96 | synchronized(this) {
|
---|
97 | i = interval;
|
---|
98 | }
|
---|
99 | sleep(1000 * i);
|
---|
100 | if(SSHClient.this.controller != null) {
|
---|
101 | ignmsg = new SSHPduOutputStream(MSG_DEBUG, controller.sndCipher);
|
---|
102 | ignmsg.writeString("heartbeat");
|
---|
103 | controller.transmit(ignmsg);
|
---|
104 | }
|
---|
105 | } catch (Exception e) {
|
---|
106 | // !!!
|
---|
107 | }
|
---|
108 | }
|
---|
109 | }
|
---|
110 | }
|
---|
111 |
|
---|
112 | // Local port forwarding
|
---|
113 | //
|
---|
114 | public static class LocalForward {
|
---|
115 | protected String localHost;
|
---|
116 | protected int localPort;
|
---|
117 | protected String remoteHost;
|
---|
118 | protected int remotePort;
|
---|
119 | protected String plugin;
|
---|
120 | public LocalForward(String localHost, int localPort, String remoteHost, int remotePort, String plugin) {
|
---|
121 | this.localHost = localHost;
|
---|
122 | this.localPort = localPort;
|
---|
123 | this.remoteHost = remoteHost;
|
---|
124 | this.remotePort = remotePort;
|
---|
125 | this.plugin = plugin;
|
---|
126 | }
|
---|
127 | }
|
---|
128 |
|
---|
129 | // Remote port forwarding
|
---|
130 | //
|
---|
131 | public static class RemoteForward {
|
---|
132 | protected int remotePort;
|
---|
133 | protected String localHost;
|
---|
134 | protected int localPort;
|
---|
135 | protected String plugin;
|
---|
136 | public RemoteForward(int remotePort, String localHost, int localPort, String plugin) {
|
---|
137 | this.remotePort = remotePort;
|
---|
138 | this.localHost = localHost;
|
---|
139 | this.localPort = localPort;
|
---|
140 | this.plugin = plugin;
|
---|
141 | }
|
---|
142 | }
|
---|
143 |
|
---|
144 | protected Thread myThread;
|
---|
145 | protected KeepAliveThread keepAliveThread;
|
---|
146 |
|
---|
147 | protected InetAddress serverAddr;
|
---|
148 | protected InetAddress serverRealAddr = null;
|
---|
149 | protected InetAddress localAddr;
|
---|
150 | protected String srvVersionStr;
|
---|
151 | protected int srvVersionMajor;
|
---|
152 | protected int srvVersionMinor;
|
---|
153 |
|
---|
154 | protected Vector localForwards;
|
---|
155 | protected Vector remoteForwards;
|
---|
156 | protected String commandLine;
|
---|
157 |
|
---|
158 | protected SSHChannelController controller;
|
---|
159 | protected SSHConsole console;
|
---|
160 | protected SSHAuthenticator authenticator;
|
---|
161 | protected SSHClientUser user;
|
---|
162 | protected SSHInteractor interactor;
|
---|
163 |
|
---|
164 | protected Socket sshSocket;
|
---|
165 | protected BufferedInputStream sshIn;
|
---|
166 | protected BufferedOutputStream sshOut;
|
---|
167 |
|
---|
168 | protected boolean gracefulExit;
|
---|
169 | protected boolean isConnected;
|
---|
170 | protected boolean isOpened;
|
---|
171 |
|
---|
172 | boolean usedOTP;
|
---|
173 |
|
---|
174 | protected int refCount;
|
---|
175 |
|
---|
176 | // !!! KLUDGE
|
---|
177 | protected boolean havePORTFtp = false;
|
---|
178 | protected int firstFTPPort = 0;
|
---|
179 | protected boolean activateTunnels = true;
|
---|
180 | // !!! KLUDGE
|
---|
181 |
|
---|
182 | public SSHClient(SSHAuthenticator authenticator, SSHClientUser user) {
|
---|
183 | this.user = user;
|
---|
184 | this.authenticator = authenticator;
|
---|
185 | this.interactor = user.getInteractor();
|
---|
186 | this.srvVersionStr = null;
|
---|
187 | this.refCount = 0;
|
---|
188 | this.usedOTP = false;
|
---|
189 |
|
---|
190 | try {
|
---|
191 | this.localAddr = InetAddress.getByName("0.0.0.0");
|
---|
192 | } catch (UnknownHostException e) {
|
---|
193 | if(interactor != null)
|
---|
194 | interactor.alert("FATAL: Could not create local InetAddress: " + e.getMessage());
|
---|
195 | }
|
---|
196 | clearAllForwards();
|
---|
197 | }
|
---|
198 |
|
---|
199 | public void setConsole(SSHConsole console) {
|
---|
200 | this.console = console;
|
---|
201 | if(controller != null)
|
---|
202 | controller.console = console;
|
---|
203 | }
|
---|
204 |
|
---|
205 | public SSHConsole getConsole() {
|
---|
206 | return console;
|
---|
207 | }
|
---|
208 |
|
---|
209 | public InetAddress getServerAddr() {
|
---|
210 | return serverAddr;
|
---|
211 | }
|
---|
212 |
|
---|
213 | public InetAddress getServerRealAddr() {
|
---|
214 | if(serverRealAddr == null)
|
---|
215 | return serverAddr;
|
---|
216 | return serverRealAddr;
|
---|
217 | }
|
---|
218 |
|
---|
219 | public void setServerRealAddr(InetAddress realAddr) {
|
---|
220 | serverRealAddr = realAddr;
|
---|
221 | }
|
---|
222 |
|
---|
223 | public InetAddress getLocalAddr() {
|
---|
224 | return localAddr;
|
---|
225 | }
|
---|
226 |
|
---|
227 | public void setLocalAddr(String addr) throws UnknownHostException {
|
---|
228 | localAddr = InetAddress.getByName(addr);
|
---|
229 | }
|
---|
230 |
|
---|
231 | public String getServerVersion() {
|
---|
232 | return srvVersionStr;
|
---|
233 | }
|
---|
234 |
|
---|
235 | public void addLocalPortForward(int localPort, String remoteHost, int remotePort, String plugin)
|
---|
236 | throws IOException {
|
---|
237 | addLocalPortForward(localAddr.getHostAddress(), localPort, remoteHost, remotePort, plugin);
|
---|
238 | }
|
---|
239 | public void addLocalPortForward(String localHost, int localPort, String remoteHost, int remotePort, String plugin)
|
---|
240 | throws IOException {
|
---|
241 | delLocalPortForward(localHost, localPort);
|
---|
242 | localForwards.addElement(new LocalForward(localHost, localPort, remoteHost, remotePort, plugin));
|
---|
243 | if(isOpened) {
|
---|
244 | try {
|
---|
245 | requestLocalPortForward(localHost, localPort, remoteHost, remotePort, plugin);
|
---|
246 | } catch(IOException e) {
|
---|
247 | delLocalPortForward(localHost, localPort);
|
---|
248 | throw e;
|
---|
249 | }
|
---|
250 | }
|
---|
251 | }
|
---|
252 |
|
---|
253 | public void delLocalPortForward(String localHost, int port) {
|
---|
254 | if(port == -1) {
|
---|
255 | if(isOpened)
|
---|
256 | controller.killListenChannels();
|
---|
257 | localForwards = new Vector();
|
---|
258 | } else {
|
---|
259 | for(int i = 0; i < localForwards.size(); i++) {
|
---|
260 | LocalForward fwd = (LocalForward) localForwards.elementAt(i);
|
---|
261 | if(fwd.localPort == port && fwd.localHost.equals(localHost)) {
|
---|
262 | localForwards.removeElementAt(i);
|
---|
263 | if(isOpened)
|
---|
264 | controller.killListenChannel(fwd.localHost, fwd.localPort);
|
---|
265 | break;
|
---|
266 | }
|
---|
267 | }
|
---|
268 | }
|
---|
269 | }
|
---|
270 |
|
---|
271 | public void addRemotePortForward(int remotePort, String localHost, int localPort, String plugin) {
|
---|
272 | delRemotePortForward(remotePort);
|
---|
273 | remoteForwards.addElement(new RemoteForward(remotePort, localHost, localPort, plugin));
|
---|
274 | }
|
---|
275 |
|
---|
276 | public void delRemotePortForward(int port) {
|
---|
277 | if(port == -1) {
|
---|
278 | remoteForwards = new Vector();
|
---|
279 | } else {
|
---|
280 | for(int i = 0; i < remoteForwards.size(); i++) {
|
---|
281 | RemoteForward fwd = (RemoteForward) remoteForwards.elementAt(i);
|
---|
282 | if(fwd.remotePort == port) {
|
---|
283 | remoteForwards.removeElementAt(i);
|
---|
284 | break;
|
---|
285 | }
|
---|
286 | }
|
---|
287 | }
|
---|
288 | }
|
---|
289 |
|
---|
290 | public void delRemotePortForward(String plugin) {
|
---|
291 | for(int i = 0; i < remoteForwards.size(); i++) {
|
---|
292 | RemoteForward fwd = (RemoteForward) remoteForwards.elementAt(i);
|
---|
293 | if(fwd.plugin.equals(plugin)) {
|
---|
294 | remoteForwards.removeElementAt(i);
|
---|
295 | i--;
|
---|
296 | }
|
---|
297 | }
|
---|
298 | }
|
---|
299 |
|
---|
300 | public void clearAllForwards() {
|
---|
301 | this.localForwards = new Vector();
|
---|
302 | this.remoteForwards = new Vector();
|
---|
303 | }
|
---|
304 |
|
---|
305 | public void startExitMonitor() {
|
---|
306 | startExitMonitor(0);
|
---|
307 | }
|
---|
308 |
|
---|
309 | public void startExitMonitor(long msTimeout) {
|
---|
310 | (new Thread(new ExitMonitor(this, msTimeout))).start();
|
---|
311 | }
|
---|
312 |
|
---|
313 | public synchronized int addRef() {
|
---|
314 | return ++refCount;
|
---|
315 | }
|
---|
316 |
|
---|
317 | public void forcedDisconnect() {
|
---|
318 | if(controller != null)
|
---|
319 | controller.sendDisconnect("exit");
|
---|
320 | else if(interactor != null)
|
---|
321 | interactor.disconnected(this, false);
|
---|
322 | }
|
---|
323 |
|
---|
324 | public synchronized int delRef() {
|
---|
325 | if(--refCount <= 0) {
|
---|
326 | forcedDisconnect();
|
---|
327 | waitForExit(2000);
|
---|
328 | }
|
---|
329 | return refCount;
|
---|
330 | }
|
---|
331 |
|
---|
332 | public void waitForExit(long msTimeout) {
|
---|
333 | try {
|
---|
334 | controller.waitForExit(msTimeout);
|
---|
335 | } catch(InterruptedException e) {
|
---|
336 | if(interactor != null)
|
---|
337 | interactor.alert("Error when shutting down SSHClient: " + e.getMessage());
|
---|
338 | controller.killAll();
|
---|
339 | }
|
---|
340 | try {
|
---|
341 | if(sshSocket != null)
|
---|
342 | sshSocket.close();
|
---|
343 | } catch (IOException e) {
|
---|
344 | // !!!
|
---|
345 | }
|
---|
346 | }
|
---|
347 |
|
---|
348 | public void doSingleCommand(String commandLine, boolean background, long msTimeout)
|
---|
349 | throws IOException {
|
---|
350 | this.commandLine = commandLine;
|
---|
351 | bootSSH(false);
|
---|
352 | if(background)
|
---|
353 | startExitMonitor(msTimeout);
|
---|
354 | else
|
---|
355 | waitForExit(msTimeout);
|
---|
356 | }
|
---|
357 |
|
---|
358 | public void bootSSH(boolean haveCnxWatch) throws IOException {
|
---|
359 | try {
|
---|
360 | myThread = Thread.currentThread();
|
---|
361 |
|
---|
362 | // Give the interactor a chance to hold us until the user wants to
|
---|
363 | // "connect" (e.g. with a dialog with server, username, password,
|
---|
364 | // proxy-info)
|
---|
365 | //
|
---|
366 | if(interactor != null)
|
---|
367 | interactor.startNewSession(this);
|
---|
368 |
|
---|
369 | // We first ask for the ssh server address since this might
|
---|
370 | // typically be a prompt in the SSHClientUser
|
---|
371 | //
|
---|
372 | String serverAddrStr = user.getSrvHost();
|
---|
373 |
|
---|
374 | // When the SSHClientUser has reported which host to connect to we report
|
---|
375 | // this to the interactor as sessionStarted
|
---|
376 | //
|
---|
377 | if(interactor != null)
|
---|
378 | interactor.sessionStarted(this);
|
---|
379 |
|
---|
380 | // It's the responsibility of the SSHClientUser to establish a proxied
|
---|
381 | // connection if that is needed, the SSHClient does not want to know about
|
---|
382 | // proxies. If a proxy is not needed getProxyConnection() just returns
|
---|
383 | // null.
|
---|
384 | //
|
---|
385 | sshSocket = user.getProxyConnection();
|
---|
386 |
|
---|
387 | if(sshSocket == null) {
|
---|
388 | serverAddr = InetAddress.getByName(serverAddrStr);
|
---|
389 | if(user.wantPrivileged()) {
|
---|
390 | int p;
|
---|
391 | for(p = 1023; p > 512; p--) {
|
---|
392 | try {
|
---|
393 | sshSocket = new Socket(serverAddr, user.getSrvPort(), localAddr, p);
|
---|
394 | } catch (IOException e) {
|
---|
395 | if(e.getMessage().toLowerCase().indexOf("use") == -1)
|
---|
396 | throw e;
|
---|
397 | continue;
|
---|
398 | }
|
---|
399 | break;
|
---|
400 | }
|
---|
401 | if(p == 512)
|
---|
402 | throw new IOException("No available privileged ports");
|
---|
403 | } else {
|
---|
404 | sshSocket = new Socket(serverAddr, user.getSrvPort());
|
---|
405 | }
|
---|
406 | } else {
|
---|
407 | serverAddr = sshSocket.getInetAddress();
|
---|
408 | if(interactor != null)
|
---|
409 | interactor.report("Connecting through proxy at " + serverAddr.getHostAddress() +
|
---|
410 | ":" + sshSocket.getPort());
|
---|
411 | }
|
---|
412 |
|
---|
413 | sshIn = new BufferedInputStream(sshSocket.getInputStream(), 8192);
|
---|
414 | sshOut = new BufferedOutputStream(sshSocket.getOutputStream());
|
---|
415 |
|
---|
416 | negotiateVersion();
|
---|
417 |
|
---|
418 | // We now have a physical connection to a sshd, report this to the SSHClientUser
|
---|
419 | //
|
---|
420 | isConnected = true;
|
---|
421 | if(interactor != null)
|
---|
422 | interactor.connected(this);
|
---|
423 |
|
---|
424 | String userName = authenticator.getUsername(user);
|
---|
425 |
|
---|
426 | receiveServerData();
|
---|
427 |
|
---|
428 | initiatePlugins();
|
---|
429 |
|
---|
430 | cipherType = authenticator.getCipher(user);
|
---|
431 |
|
---|
432 | // Check that selected cipher is supported by server
|
---|
433 | //
|
---|
434 | if(!isCipherSupported(cipherType))
|
---|
435 | throw new IOException("Sorry, server does not support the '" +
|
---|
436 | getCipherName(authenticator.getCipher(user)) + "' cipher.");
|
---|
437 |
|
---|
438 | generateSessionId();
|
---|
439 | generateSessionKey();
|
---|
440 |
|
---|
441 | initClientCipher();
|
---|
442 |
|
---|
443 | sendSessionKey(cipherType);
|
---|
444 |
|
---|
445 | // !!!
|
---|
446 | // At this stage the communication is encrypted
|
---|
447 | // !!!
|
---|
448 |
|
---|
449 | authenticateUser(userName);
|
---|
450 |
|
---|
451 | controller = new SSHChannelController(this, sshIn, sshOut, sndCipher, rcvCipher,
|
---|
452 | console, haveCnxWatch);
|
---|
453 | initiateSession();
|
---|
454 | if(console != null)
|
---|
455 | console.serverConnect(controller, sndCipher);
|
---|
456 |
|
---|
457 | // We now open the SSH-protocol fully, report to SSHClientUser
|
---|
458 | //
|
---|
459 | isOpened = true;
|
---|
460 | if(interactor != null)
|
---|
461 | interactor.open(this);
|
---|
462 |
|
---|
463 | // Start "heartbeat" if needed
|
---|
464 | //
|
---|
465 | setAliveInterval(user.getAliveInterval());
|
---|
466 |
|
---|
467 | controller.start();
|
---|
468 |
|
---|
469 | } catch (IOException e) {
|
---|
470 | if(sshSocket != null)
|
---|
471 | sshSocket.close();
|
---|
472 | disconnect(false);
|
---|
473 | if(controller != null) {
|
---|
474 | controller.killListenChannels();
|
---|
475 | }
|
---|
476 | controller = null;
|
---|
477 | throw e;
|
---|
478 | }
|
---|
479 | }
|
---|
480 |
|
---|
481 | protected void disconnect(boolean graceful) {
|
---|
482 | if(!isConnected)
|
---|
483 | return;
|
---|
484 | isConnected = false;
|
---|
485 | isOpened = false;
|
---|
486 | gracefulExit = graceful;
|
---|
487 | srvVersionStr = null;
|
---|
488 | setAliveInterval(0); // Stop "heartbeat"...
|
---|
489 | if(interactor != null)
|
---|
490 | interactor.disconnected(this, graceful);
|
---|
491 | }
|
---|
492 |
|
---|
493 | void negotiateVersion() throws IOException {
|
---|
494 | byte[] buf = new byte[256];
|
---|
495 | int len;
|
---|
496 | String verStr;
|
---|
497 |
|
---|
498 | len = sshIn.read(buf);
|
---|
499 |
|
---|
500 | srvVersionStr = new String(buf, 0, len);
|
---|
501 |
|
---|
502 | try {
|
---|
503 | int l = srvVersionStr.indexOf('-');
|
---|
504 | int r = srvVersionStr.indexOf('.');
|
---|
505 | srvVersionMajor = Integer.parseInt(srvVersionStr.substring(l + 1, r));
|
---|
506 | l = r;
|
---|
507 | r = srvVersionStr.indexOf('-', l);
|
---|
508 | if(r == -1) {
|
---|
509 | srvVersionMinor = Integer.parseInt(srvVersionStr.substring(l + 1));
|
---|
510 | } else {
|
---|
511 | srvVersionMinor = Integer.parseInt(srvVersionStr.substring(l + 1, r));
|
---|
512 | }
|
---|
513 | } catch (Throwable t) {
|
---|
514 | throw new IOException("Server version string invalid: " + srvVersionStr);
|
---|
515 | }
|
---|
516 |
|
---|
517 | if(srvVersionMajor > 1) {
|
---|
518 | throw new IOException("MindTerm do not support SSHv2 yet, enable SSHv1 compatibility in server");
|
---|
519 | } else if(srvVersionMajor < 1 || srvVersionMinor < 5) {
|
---|
520 | throw new IOException("Server's protocol version (" + srvVersionMajor + "-" +
|
---|
521 | srvVersionMinor + ") is too old, please upgrade");
|
---|
522 | }
|
---|
523 |
|
---|
524 | // Strip white-space
|
---|
525 | srvVersionStr = srvVersionStr.trim();
|
---|
526 |
|
---|
527 | verStr = getVersionId(true);
|
---|
528 | verStr += "\n";
|
---|
529 | buf = verStr.getBytes();
|
---|
530 |
|
---|
531 | sshOut.write(buf);
|
---|
532 | sshOut.flush();
|
---|
533 | }
|
---|
534 |
|
---|
535 | void receiveServerData() throws IOException {
|
---|
536 | SSHPduInputStream pdu = new SSHPduInputStream(SMSG_PUBLIC_KEY, null);
|
---|
537 | pdu.readFrom(sshIn);
|
---|
538 | int bits;
|
---|
539 | BigInteger e, n;
|
---|
540 |
|
---|
541 | srvCookie = new byte[8];
|
---|
542 | pdu.read(srvCookie, 0, 8);
|
---|
543 |
|
---|
544 | bits = pdu.readInt();
|
---|
545 | e = pdu.readBigInteger();
|
---|
546 | n = pdu.readBigInteger();
|
---|
547 | srvServerKey = new KeyPair(new RSAPublicKey(e, n), null);
|
---|
548 |
|
---|
549 | bits = pdu.readInt();
|
---|
550 | e = pdu.readBigInteger();
|
---|
551 | n = pdu.readBigInteger();
|
---|
552 | srvHostKey = new KeyPair(new RSAPublicKey(e, n), null);
|
---|
553 |
|
---|
554 | int keyLenDiff = Math.abs(((RSAPublicKey)srvServerKey.getPublic()).bitLength() -
|
---|
555 | ((RSAPublicKey)srvHostKey.getPublic()).bitLength());
|
---|
556 |
|
---|
557 | if(keyLenDiff < 24) {
|
---|
558 | throw new IOException("Invalid server keys, difference in sizes must be at least 24 bits");
|
---|
559 | }
|
---|
560 |
|
---|
561 | if(!authenticator.verifyKnownHosts((RSAPublicKey)srvHostKey.getPublic())) {
|
---|
562 | throw new IOException("Verification of known hosts failed");
|
---|
563 | }
|
---|
564 |
|
---|
565 | protocolFlags = pdu.readInt();
|
---|
566 | supportedCiphers = pdu.readInt();
|
---|
567 | supportedAuthTypes = pdu.readInt();
|
---|
568 |
|
---|
569 | // OUCH: Support SDI patch from ftp://ftp.parc.xerox.com://pub/jean/sshsdi/
|
---|
570 | // (we want the types to be in sequence for simplicity, kludge but simple)
|
---|
571 | //
|
---|
572 | if((supportedAuthTypes & (1 << 16)) != 0) {
|
---|
573 | supportedAuthTypes = ((supportedAuthTypes & 0xffff) | (1 << AUTH_SDI));
|
---|
574 | }
|
---|
575 | }
|
---|
576 |
|
---|
577 | void generateSessionKey() {
|
---|
578 | SecureRandom rand = secureRandom();
|
---|
579 | sessionKey = new byte[SESSION_KEY_LENGTH / 8];
|
---|
580 | rand.nextBytes(sessionKey);
|
---|
581 | rand.startUpdater();
|
---|
582 | }
|
---|
583 |
|
---|
584 | void sendSessionKey(int cipherType) throws IOException {
|
---|
585 | byte[] key = new byte[sessionKey.length + 1];
|
---|
586 | BigInteger encKey;
|
---|
587 | RSACipher rsa;
|
---|
588 | SSHPduOutputStream pdu;
|
---|
589 |
|
---|
590 | key[0] = 0;
|
---|
591 | System.arraycopy(sessionKey, 0, key, 1, sessionKey.length);
|
---|
592 |
|
---|
593 | for(int i = 0; i < sessionId.length; i++)
|
---|
594 | key[i + 1] ^= sessionId[i];
|
---|
595 |
|
---|
596 | encKey = new BigInteger(key);
|
---|
597 |
|
---|
598 | if(((RSAPublicKey)(srvServerKey.getPublic())).bitLength() <
|
---|
599 | ((RSAPublicKey)(srvHostKey.getPublic())).bitLength()) {
|
---|
600 | BigInteger padded;
|
---|
601 | rsa = new RSACipher(srvServerKey);
|
---|
602 | padded = rsa.doPad(encKey, ((RSAPublicKey)srvServerKey.getPublic()).bitLength(), secureRandom());
|
---|
603 | encKey = rsa.doPublic(padded);
|
---|
604 | rsa = new RSACipher(srvHostKey);
|
---|
605 | padded = rsa.doPad(encKey, ((RSAPublicKey)srvHostKey.getPublic()).bitLength(), secureRandom());
|
---|
606 | encKey = rsa.doPublic(padded);
|
---|
607 | } else {
|
---|
608 | BigInteger padded;
|
---|
609 | rsa = new RSACipher(srvHostKey);
|
---|
610 | padded = rsa.doPad(encKey, ((RSAPublicKey)srvHostKey.getPublic()).bitLength(), secureRandom());
|
---|
611 | encKey = rsa.doPublic(padded);
|
---|
612 | rsa = new RSACipher(srvServerKey);
|
---|
613 | padded = rsa.doPad(encKey, ((RSAPublicKey)srvServerKey.getPublic()).bitLength(), secureRandom());
|
---|
614 | encKey = rsa.doPublic(padded);
|
---|
615 | }
|
---|
616 |
|
---|
617 | pdu = new SSHPduOutputStream(CMSG_SESSION_KEY, null);
|
---|
618 | pdu.writeByte((byte)cipherType);
|
---|
619 | pdu.write(srvCookie, 0, srvCookie.length);
|
---|
620 | pdu.writeBigInteger(encKey);
|
---|
621 | // !!! TODO: check this pdu.writeInt(PROTOFLAG_SCREEN_NUMBER | PROTOFLAG_HOST_IN_FWD_OPEN);
|
---|
622 | pdu.writeInt(protocolFlags);
|
---|
623 | pdu.writeTo(sshOut);
|
---|
624 |
|
---|
625 | // !!!
|
---|
626 | // At this stage the communication is encrypted
|
---|
627 | // !!!
|
---|
628 |
|
---|
629 | if(!isSuccess())
|
---|
630 | throw new IOException("Error while sending session key!");
|
---|
631 | }
|
---|
632 |
|
---|
633 | void authenticateUser(String userName) throws IOException {
|
---|
634 | SSHPduOutputStream outpdu;
|
---|
635 |
|
---|
636 | usedOTP = false;
|
---|
637 |
|
---|
638 | outpdu = new SSHPduOutputStream(CMSG_USER, sndCipher);
|
---|
639 | outpdu.writeString(userName);
|
---|
640 | outpdu.writeTo(sshOut);
|
---|
641 |
|
---|
642 | if(isSuccess()) {
|
---|
643 | if(interactor != null)
|
---|
644 | interactor.report("Authenticated directly by server, no other authentication required");
|
---|
645 | return;
|
---|
646 | }
|
---|
647 |
|
---|
648 | int[] authType = authenticator.getAuthTypes(user);
|
---|
649 |
|
---|
650 | for(int i = 0; i < authType.length; i++) {
|
---|
651 | try {
|
---|
652 | if(!isAuthTypeSupported(authType[i])) {
|
---|
653 | throw new AuthFailException("Server does not support '" +
|
---|
654 | authTypeDesc[authType[i]] + "'");
|
---|
655 | }
|
---|
656 | switch(authType[i]) {
|
---|
657 | case AUTH_RSA:
|
---|
658 | doRSAAuth(false, userName);
|
---|
659 | break;
|
---|
660 | case AUTH_PASSWORD:
|
---|
661 | doPasswdAuth(userName);
|
---|
662 | break;
|
---|
663 | case AUTH_RHOSTS_RSA:
|
---|
664 | doRSAAuth(true, userName);
|
---|
665 | break;
|
---|
666 | case AUTH_TIS:
|
---|
667 | doTISAuth(userName);
|
---|
668 | break;
|
---|
669 | case AUTH_RHOSTS:
|
---|
670 | doRhostsAuth(userName);
|
---|
671 | break;
|
---|
672 | case AUTH_SDI:
|
---|
673 | doSDIAuth(userName);
|
---|
674 | usedOTP = true;
|
---|
675 | break;
|
---|
676 | case AUTH_KERBEROS:
|
---|
677 | case PASS_KERBEROS_TGT:
|
---|
678 | default:
|
---|
679 | throw new IOException("We do not support selected authentication type " +
|
---|
680 | authTypeDesc[authType[i]]);
|
---|
681 | }
|
---|
682 | return;
|
---|
683 | } catch (AuthFailException e) {
|
---|
684 | if(i == (authType.length - 1)) {
|
---|
685 | throw e;
|
---|
686 | }
|
---|
687 | if(interactor != null) {
|
---|
688 | interactor.report("Authenticating with " + authTypeDesc[authType[i]] + " failed, " +
|
---|
689 | e.getMessage());
|
---|
690 | }
|
---|
691 | }
|
---|
692 | }
|
---|
693 | }
|
---|
694 |
|
---|
695 | void doPasswdAuth(String userName) throws IOException {
|
---|
696 | SSHPduOutputStream outpdu;
|
---|
697 | String password;
|
---|
698 |
|
---|
699 | password = authenticator.getPassword(user);
|
---|
700 |
|
---|
701 | outpdu = new SSHPduOutputStream(CMSG_AUTH_PASSWORD, sndCipher);
|
---|
702 | outpdu.writeString(password);
|
---|
703 | outpdu.writeTo(sshOut);
|
---|
704 |
|
---|
705 | if(!isSuccess())
|
---|
706 | throw new AuthFailException("Permission denied");
|
---|
707 | }
|
---|
708 |
|
---|
709 | void doRhostsAuth(String userName) throws IOException {
|
---|
710 | SSHPduOutputStream outpdu;
|
---|
711 |
|
---|
712 | outpdu = new SSHPduOutputStream(CMSG_AUTH_RHOSTS, sndCipher);
|
---|
713 | outpdu.writeString(userName);
|
---|
714 | outpdu.writeTo(sshOut);
|
---|
715 |
|
---|
716 | if(!isSuccess())
|
---|
717 | throw new AuthFailException("Permission denied");
|
---|
718 | }
|
---|
719 |
|
---|
720 | void doTISAuth(String userName) throws IOException {
|
---|
721 | SSHPduOutputStream outpdu;
|
---|
722 | String prompt;
|
---|
723 | String response;
|
---|
724 |
|
---|
725 | outpdu = new SSHPduOutputStream(CMSG_AUTH_TIS, sndCipher);
|
---|
726 | outpdu.writeTo(sshOut);
|
---|
727 | SSHPduInputStream inpdu = new SSHPduInputStream(MSG_ANY, rcvCipher);
|
---|
728 | inpdu.readFrom(sshIn);
|
---|
729 |
|
---|
730 | if(inpdu.type == SMSG_FAILURE)
|
---|
731 | throw new AuthFailException("TIS authentication server not reachable or user unknown");
|
---|
732 | else if(inpdu.type != SMSG_AUTH_TIS_CHALLENGE)
|
---|
733 | throw new IOException("Protocol error, expected TIS challenge but got " + inpdu.type);
|
---|
734 |
|
---|
735 | prompt = inpdu.readString();
|
---|
736 | response = authenticator.getChallengeResponse(user, prompt);
|
---|
737 |
|
---|
738 | outpdu = new SSHPduOutputStream(CMSG_AUTH_TIS_RESPONSE, sndCipher);
|
---|
739 | outpdu.writeString(response);
|
---|
740 | outpdu.writeTo(sshOut);
|
---|
741 |
|
---|
742 | if(!isSuccess())
|
---|
743 | throw new AuthFailException("Permission denied");
|
---|
744 | }
|
---|
745 |
|
---|
746 | void doRSAAuth(boolean rhosts, String userName) throws IOException {
|
---|
747 | SSHPduOutputStream outpdu;
|
---|
748 | SSHRSAKeyFile keyFile = authenticator.getIdentityFile(user);
|
---|
749 | RSAPublicKey pubKey = keyFile.getPublic();
|
---|
750 |
|
---|
751 | if(rhosts) {
|
---|
752 | outpdu = new SSHPduOutputStream(CMSG_AUTH_RHOSTS_RSA, sndCipher);
|
---|
753 | outpdu.writeString(userName);
|
---|
754 | outpdu.writeInt(pubKey.bitLength());
|
---|
755 | outpdu.writeBigInteger(pubKey.getE());
|
---|
756 | outpdu.writeBigInteger(pubKey.getN());
|
---|
757 | } else {
|
---|
758 | outpdu = new SSHPduOutputStream(CMSG_AUTH_RSA, sndCipher);
|
---|
759 | outpdu.writeBigInteger(pubKey.getN());
|
---|
760 | }
|
---|
761 | outpdu.writeTo(sshOut);
|
---|
762 |
|
---|
763 | SSHPduInputStream inpdu = new SSHPduInputStream(MSG_ANY, rcvCipher);
|
---|
764 | inpdu.readFrom(sshIn);
|
---|
765 | if(inpdu.type == SMSG_FAILURE)
|
---|
766 | throw new AuthFailException("Server refused our key" + (rhosts ? " or rhosts" : ""));
|
---|
767 | else if(inpdu.type != SMSG_AUTH_RSA_CHALLENGE)
|
---|
768 | throw new IOException("Protocol error, expected RSA-challenge but got " + inpdu.type);
|
---|
769 |
|
---|
770 | BigInteger challenge = inpdu.readBigInteger();
|
---|
771 |
|
---|
772 | // First try with an empty passphrase...
|
---|
773 | //
|
---|
774 | RSAPrivateKey privKey = keyFile.getPrivate("");
|
---|
775 | if(privKey == null)
|
---|
776 | privKey = keyFile.getPrivate(authenticator.getIdentityPassword(user));
|
---|
777 | else if(interactor != null)
|
---|
778 | interactor.report("Authenticated with password-less rsa-key '" + keyFile.getComment() + "'");
|
---|
779 |
|
---|
780 | if(privKey == null)
|
---|
781 | throw new AuthFailException("Invalid password for key-file '" + keyFile.getComment() + "'");
|
---|
782 |
|
---|
783 | rsaChallengeResponse(privKey, challenge);
|
---|
784 | }
|
---|
785 |
|
---|
786 | private final static int CANNOT_CHOOSE_PIN = 0;
|
---|
787 | private final static int USER_SELECTABLE = 1;
|
---|
788 | private final static int MUST_CHOOSE_PIN = 2;
|
---|
789 |
|
---|
790 | void doSDIAuth(String userName) throws IOException {
|
---|
791 | SSHPduOutputStream outpdu;
|
---|
792 | String password;
|
---|
793 |
|
---|
794 | password = authenticator.getChallengeResponse(user, userName +
|
---|
795 | "'s SDI token passcode: ");
|
---|
796 |
|
---|
797 | outpdu = new SSHPduOutputStream(CMSG_AUTH_SDI, sndCipher);
|
---|
798 | outpdu.writeString(password);
|
---|
799 | outpdu.writeTo(sshOut);
|
---|
800 |
|
---|
801 | SSHPduInputStream inpdu = new SSHPduInputStream(MSG_ANY, rcvCipher);
|
---|
802 | inpdu.readFrom(sshIn);
|
---|
803 | switch(inpdu.type) {
|
---|
804 | case SMSG_SUCCESS:
|
---|
805 | interactor.report("SDI authentication accepted.");
|
---|
806 | break;
|
---|
807 |
|
---|
808 | case SMSG_FAILURE:
|
---|
809 | throw new AuthFailException("SDI authentication failed.");
|
---|
810 |
|
---|
811 | case CMSG_ACM_NEXT_CODE_REQUIRED:
|
---|
812 | password = interactor.promptPassword("Next token required: ");
|
---|
813 | outpdu = new SSHPduOutputStream(CMSG_ACM_NEXT_CODE, sndCipher);
|
---|
814 | outpdu.writeString(password);
|
---|
815 | outpdu.writeTo(sshOut);
|
---|
816 | if(!isSuccess())
|
---|
817 | throw new AuthFailException("Permission denied");
|
---|
818 | break;
|
---|
819 |
|
---|
820 | case CMSG_ACM_NEW_PIN_REQUIRED:
|
---|
821 | if(!interactor.askConfirmation("New PIN required, do you want to continue?", false))
|
---|
822 | throw new AuthFailException("New PIN not wanted");
|
---|
823 |
|
---|
824 | String type = inpdu.readString();
|
---|
825 | String size = inpdu.readString();
|
---|
826 | int userSelect = inpdu.readInt();
|
---|
827 |
|
---|
828 | switch(userSelect) {
|
---|
829 | case CANNOT_CHOOSE_PIN:
|
---|
830 | break;
|
---|
831 |
|
---|
832 | case USER_SELECTABLE:
|
---|
833 | case MUST_CHOOSE_PIN:
|
---|
834 | String pwdChk;
|
---|
835 | do {
|
---|
836 | password =
|
---|
837 | interactor.promptPassword("Please enter new PIN" +
|
---|
838 | " containing " + size +
|
---|
839 | " " + type);
|
---|
840 | pwdChk =
|
---|
841 | interactor.promptPassword("Please enter new PIN again");
|
---|
842 | } while (!password.equals(pwdChk));
|
---|
843 |
|
---|
844 | outpdu = new SSHPduOutputStream(CMSG_ACM_NEW_PIN, sndCipher);
|
---|
845 | outpdu.writeString(password);
|
---|
846 | outpdu.writeTo(sshOut);
|
---|
847 |
|
---|
848 | inpdu = new SSHPduInputStream(MSG_ANY, rcvCipher);
|
---|
849 | inpdu.readFrom(sshIn);
|
---|
850 | if(inpdu.type != CMSG_ACM_NEW_PIN_ACCEPTED) {
|
---|
851 | throw new AuthFailException("PIN rejected by server");
|
---|
852 | }
|
---|
853 | throw new AuthFailException("New PIN accepted, " +
|
---|
854 | "Wait for the code on your token to change");
|
---|
855 |
|
---|
856 | default:
|
---|
857 | throw new AuthFailException("Invalid response from server");
|
---|
858 | }
|
---|
859 |
|
---|
860 | break;
|
---|
861 |
|
---|
862 | case CMSG_ACM_ACCESS_DENIED:
|
---|
863 | // Fall through
|
---|
864 | default:
|
---|
865 | throw new AuthFailException("Permission denied");
|
---|
866 | }
|
---|
867 | }
|
---|
868 |
|
---|
869 | void rsaChallengeResponse(RSAPrivateKey privKey, BigInteger challenge) throws IOException {
|
---|
870 | RSACipher rsa = new RSACipher(new KeyPair(null, privKey));
|
---|
871 | MessageDigest md5;
|
---|
872 |
|
---|
873 | challenge = rsa.doPrivate(challenge);
|
---|
874 | challenge = rsa.stripPad(challenge);
|
---|
875 | byte[] response = challenge.toByteArray();
|
---|
876 |
|
---|
877 | try {
|
---|
878 | md5 = MessageDigest.getInstance("MD5");
|
---|
879 | if(response[0] == 0)
|
---|
880 | md5.update(response, 1, 32);
|
---|
881 | else
|
---|
882 | md5.update(response, 0, 32);
|
---|
883 | md5.update(sessionId);
|
---|
884 | response = md5.digest();
|
---|
885 | } catch(Exception e) {
|
---|
886 | throw new IOException("MD5 not implemented, can't generate session-id");
|
---|
887 | }
|
---|
888 |
|
---|
889 | SSHPduOutputStream outpdu = new SSHPduOutputStream(CMSG_AUTH_RSA_RESPONSE, sndCipher);
|
---|
890 | outpdu.write(response, 0, response.length);
|
---|
891 | outpdu.writeTo(sshOut);
|
---|
892 |
|
---|
893 | if(!isSuccess())
|
---|
894 | throw new AuthFailException("Permission denied");
|
---|
895 | }
|
---|
896 |
|
---|
897 | void initiateSession() throws IOException {
|
---|
898 | // !!! java.util.zip.Deflater/Inflater can't be used since we can't give
|
---|
899 | // the native inflate/deflate methods the Z_PARTIAL_FLUSH flag
|
---|
900 | // requestCompression(3);
|
---|
901 |
|
---|
902 | if(user.wantPTY())
|
---|
903 | requestPTY();
|
---|
904 |
|
---|
905 | int maxPktSz = user.getMaxPacketSz();
|
---|
906 | if(maxPktSz > 0)
|
---|
907 | requestMaxPacketSz(maxPktSz);
|
---|
908 |
|
---|
909 | if(user.wantX11Forward())
|
---|
910 | requestX11Forward();
|
---|
911 |
|
---|
912 | if(activateTunnels)
|
---|
913 | initiateTunnels();
|
---|
914 |
|
---|
915 | if(commandLine != null)
|
---|
916 | requestCommand(commandLine);
|
---|
917 | else
|
---|
918 | requestShell();
|
---|
919 |
|
---|
920 | // !!!
|
---|
921 | // At this stage we can't send more options/forward-requests
|
---|
922 | // the server has now entered it's service-loop.
|
---|
923 | }
|
---|
924 |
|
---|
925 | void initiatePlugins() {
|
---|
926 | SSHProtocolPlugin.initiateAll(this);
|
---|
927 | }
|
---|
928 |
|
---|
929 | void initiateTunnels() throws IOException {
|
---|
930 | int i;
|
---|
931 | for(i = 0; i < localForwards.size(); i++) {
|
---|
932 | LocalForward fwd = (LocalForward) localForwards.elementAt(i);
|
---|
933 | requestLocalPortForward(fwd.localHost, fwd.localPort, fwd.remoteHost, fwd.remotePort, fwd.plugin);
|
---|
934 | }
|
---|
935 | for(i = 0; i < remoteForwards.size(); i++) {
|
---|
936 | RemoteForward fwd = (RemoteForward) remoteForwards.elementAt(i);
|
---|
937 | requestRemotePortForward(fwd.remotePort, fwd.localHost, fwd.localPort, fwd.plugin);
|
---|
938 | }
|
---|
939 | }
|
---|
940 |
|
---|
941 | void requestCompression(int level) throws IOException {
|
---|
942 | SSHPduOutputStream outpdu = new SSHPduOutputStream(CMSG_REQUEST_COMPRESSION, sndCipher);
|
---|
943 | outpdu.writeInt(level);
|
---|
944 | outpdu.writeTo(sshOut);
|
---|
945 | if(!isSuccess() && interactor != null)
|
---|
946 | interactor.report("Error requesting compression level: " + level);
|
---|
947 | }
|
---|
948 |
|
---|
949 | void requestMaxPacketSz(int sz) throws IOException {
|
---|
950 | SSHPduOutputStream outpdu = new SSHPduOutputStream(CMSG_MAX_PACKET_SIZE, sndCipher);
|
---|
951 | outpdu.writeInt(sz);
|
---|
952 | outpdu.writeTo(sshOut);
|
---|
953 | if(!isSuccess() && interactor != null)
|
---|
954 | interactor.report("Error requesting max packet size: " + sz);
|
---|
955 | }
|
---|
956 |
|
---|
957 | void requestX11Forward() throws IOException {
|
---|
958 | SSHPduOutputStream outpdu = new SSHPduOutputStream(CMSG_X11_REQUEST_FORWARDING, sndCipher);
|
---|
959 |
|
---|
960 | // !!!
|
---|
961 | outpdu.writeString("MIT-MAGIC-COOKIE-1");
|
---|
962 | outpdu.writeString("112233445566778899aabbccddeeff00");
|
---|
963 | outpdu.writeInt(0);
|
---|
964 | // !!!
|
---|
965 |
|
---|
966 | outpdu.writeTo(sshOut);
|
---|
967 |
|
---|
968 | if(!isSuccess() && interactor != null)
|
---|
969 | interactor.report("Error requesting X11 forward");
|
---|
970 | }
|
---|
971 |
|
---|
972 | void requestPTY() throws IOException {
|
---|
973 | SSHPduOutputStream outpdu = new SSHPduOutputStream(CMSG_REQUEST_PTY, sndCipher);
|
---|
974 | Terminal myTerminal = null;
|
---|
975 | if(console != null)
|
---|
976 | myTerminal = console.getTerminal();
|
---|
977 | if(myTerminal != null) {
|
---|
978 | outpdu.writeString(myTerminal.terminalType());
|
---|
979 | outpdu.writeInt(myTerminal.rows());
|
---|
980 | outpdu.writeInt(myTerminal.cols());
|
---|
981 | outpdu.writeInt(myTerminal.vpixels());
|
---|
982 | outpdu.writeInt(myTerminal.hpixels());
|
---|
983 | } else {
|
---|
984 | outpdu.writeString("");
|
---|
985 | outpdu.writeInt(0);
|
---|
986 | outpdu.writeInt(0);
|
---|
987 | outpdu.writeInt(0);
|
---|
988 | outpdu.writeInt(0);
|
---|
989 | }
|
---|
990 | outpdu.writeByte((byte)TTY_OP_END);
|
---|
991 | outpdu.writeTo(sshOut);
|
---|
992 |
|
---|
993 | if(!isSuccess() && interactor != null)
|
---|
994 | interactor.report("Error requesting PTY");
|
---|
995 | }
|
---|
996 |
|
---|
997 | void requestLocalPortForward(String localHost, int localPort, String remoteHost, int remotePort, String plugin)
|
---|
998 | throws IOException {
|
---|
999 | controller.newListenChannel(localHost, localPort, remoteHost, remotePort, plugin);
|
---|
1000 | }
|
---|
1001 |
|
---|
1002 | void requestRemotePortForward(int remotePort, String localHost, int localPort, String plugin)
|
---|
1003 | throws IOException {
|
---|
1004 |
|
---|
1005 | try {
|
---|
1006 | SSHProtocolPlugin.getPlugin(plugin).remoteListener(remotePort, localHost, localPort,
|
---|
1007 | controller);
|
---|
1008 | } catch (NoClassDefFoundError e) {
|
---|
1009 | throw new IOException("Plugins not available");
|
---|
1010 | }
|
---|
1011 |
|
---|
1012 | SSHPduOutputStream outpdu = new SSHPduOutputStream(CMSG_PORT_FORWARD_REQUEST, sndCipher);
|
---|
1013 | outpdu.writeInt(remotePort);
|
---|
1014 | outpdu.writeString(localHost);
|
---|
1015 | outpdu.writeInt(localPort);
|
---|
1016 | outpdu.writeTo(sshOut);
|
---|
1017 |
|
---|
1018 | if(!isSuccess() && interactor != null) {
|
---|
1019 | interactor.report("Error requesting remote port forward: " + plugin +
|
---|
1020 | "/" + remotePort + ":" + localHost + ":" + localPort);
|
---|
1021 |
|
---|
1022 | }
|
---|
1023 | }
|
---|
1024 |
|
---|
1025 | void requestCommand(String command) throws IOException {
|
---|
1026 | SSHPduOutputStream outpdu = new SSHPduOutputStream(CMSG_EXEC_CMD, sndCipher);
|
---|
1027 | outpdu.writeString(command);
|
---|
1028 | outpdu.writeTo(sshOut);
|
---|
1029 | }
|
---|
1030 |
|
---|
1031 | void requestShell() throws IOException {
|
---|
1032 | SSHPduOutputStream outpdu = new SSHPduOutputStream(CMSG_EXEC_SHELL, sndCipher);
|
---|
1033 | outpdu.writeTo(sshOut);
|
---|
1034 | }
|
---|
1035 |
|
---|
1036 | boolean isSuccess() throws IOException {
|
---|
1037 | boolean success = false;
|
---|
1038 | SSHPduInputStream inpdu = null;
|
---|
1039 | inpdu = new SSHPduInputStream(MSG_ANY, rcvCipher);
|
---|
1040 | inpdu.readFrom(sshIn);
|
---|
1041 | if(inpdu.type == SMSG_SUCCESS)
|
---|
1042 | success = true;
|
---|
1043 | else if(inpdu.type == SMSG_FAILURE)
|
---|
1044 | success = false;
|
---|
1045 | else if(inpdu.type == MSG_DISCONNECT)
|
---|
1046 | throw new IOException("Server disconnected: " + inpdu.readString());
|
---|
1047 | else
|
---|
1048 | throw new IOException("Protocol error: got " + inpdu.type +
|
---|
1049 | " when expecting success/failure");
|
---|
1050 | return success;
|
---|
1051 | }
|
---|
1052 |
|
---|
1053 | void setInteractive() {
|
---|
1054 | try {
|
---|
1055 | sshSocket.setTcpNoDelay(true);
|
---|
1056 | } catch (SocketException e) {
|
---|
1057 | if(interactor != null)
|
---|
1058 | interactor.report("Error setting interactive mode: " + e.getMessage());
|
---|
1059 | }
|
---|
1060 | }
|
---|
1061 |
|
---|
1062 | void setAliveInterval(int i) {
|
---|
1063 | if(i == 0) {
|
---|
1064 | if(keepAliveThread != null && keepAliveThread.isAlive())
|
---|
1065 | keepAliveThread.stop();
|
---|
1066 | keepAliveThread = null;
|
---|
1067 | } else {
|
---|
1068 | if(keepAliveThread != null) {
|
---|
1069 | keepAliveThread.setInterval(i);
|
---|
1070 | } else {
|
---|
1071 | keepAliveThread = new KeepAliveThread(i);
|
---|
1072 | keepAliveThread.start();
|
---|
1073 | }
|
---|
1074 | }
|
---|
1075 | }
|
---|
1076 |
|
---|
1077 | public boolean isOpened() {
|
---|
1078 | return isOpened;
|
---|
1079 | }
|
---|
1080 |
|
---|
1081 | public boolean isConnected() {
|
---|
1082 | return isConnected;
|
---|
1083 | }
|
---|
1084 |
|
---|
1085 | void stdinWriteChar(char c) throws IOException {
|
---|
1086 | stdinWriteString(String.valueOf(c));
|
---|
1087 | }
|
---|
1088 |
|
---|
1089 | void stdinWriteString(String str) throws IOException {
|
---|
1090 | stdinWriteString(str.getBytes(), 0, str.length());
|
---|
1091 | }
|
---|
1092 |
|
---|
1093 | void stdinWriteString(byte[] str) throws IOException {
|
---|
1094 | stdinWriteString(str, 0, str.length);
|
---|
1095 | }
|
---|
1096 |
|
---|
1097 | void stdinWriteString(byte[] str, int off, int len) throws IOException {
|
---|
1098 | SSHPduOutputStream stdinPdu;
|
---|
1099 | if(isOpened && controller != null) {
|
---|
1100 | stdinPdu = new SSHPduOutputStream(SSH.CMSG_STDIN_DATA, sndCipher);
|
---|
1101 | stdinPdu.writeInt(len);
|
---|
1102 | stdinPdu.write(str, off, len);
|
---|
1103 | controller.transmit(stdinPdu);
|
---|
1104 | }
|
---|
1105 | }
|
---|
1106 |
|
---|
1107 | void signalWindowChanged(int rows, int cols, int vpixels, int hpixels) {
|
---|
1108 | if(isOpened && controller != null) {
|
---|
1109 | try {
|
---|
1110 | SSHPduOutputStream pdu;
|
---|
1111 | pdu = new SSHPduOutputStream(SSH.CMSG_WINDOW_SIZE, sndCipher);
|
---|
1112 | pdu.writeInt(rows);
|
---|
1113 | pdu.writeInt(cols);
|
---|
1114 | pdu.writeInt(vpixels);
|
---|
1115 | pdu.writeInt(hpixels);
|
---|
1116 | controller.transmit(pdu);
|
---|
1117 | } catch (Exception ex) {
|
---|
1118 | if(interactor != null)
|
---|
1119 | interactor.alert("Error when sending sigWinch: " + ex.toString());
|
---|
1120 | }
|
---|
1121 | }
|
---|
1122 | }
|
---|
1123 |
|
---|
1124 | }
|
---|