source: other-projects/trunk/gs3-release-maker/tasks/sshtaskdef/src/mindbright/ssh/SSHClient.java@ 14627

Last change on this file since 14627 was 14627, checked in by oranfry, 17 years ago

initial import of the gs3-release-maker

File size: 33.1 KB
Line 
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 *****************************************************************************/
21package mindbright.ssh;
22
23import java.net.*;
24import java.io.*;
25import java.math.BigInteger;
26import java.util.Vector;
27
28import mindbright.security.*;
29import 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 */
55public 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}
Note: See TracBrowser for help on using the repository browser.