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