Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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}