Updated Branches:
  refs/heads/master 36446ae8d -> ade49e489

[SSHD-283] Support key re-exchange

Make main message handler common between ServerSession and ClientSessionImpl.
Replace the Session#state with an internal kex state variable and send events 
on SessionListener instead.

Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/8d226dd8
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/8d226dd8
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/8d226dd8

Branch: refs/heads/master
Commit: 8d226dd8a2f7f234924f95e88b2206808567e681
Parents: 36446ae
Author: Guillaume Nodet <[email protected]>
Authored: Wed Jan 29 22:21:45 2014 +0100
Committer: Guillaume Nodet <[email protected]>
Committed: Wed Jan 29 22:21:45 2014 +0100

----------------------------------------------------------------------
 .../sshd/client/session/ClientSessionImpl.java  | 156 +++++----------
 .../java/org/apache/sshd/common/Session.java    |  17 +-
 .../org/apache/sshd/common/SessionListener.java |  12 +-
 .../org/apache/sshd/common/SshConstants.java    |  35 ++++
 .../session/AbstractConnectionService.java      |  39 ++--
 .../sshd/common/session/AbstractSession.java    | 192 +++++++++++++++----
 .../sshd/server/session/ServerSession.java      | 154 +--------------
 .../session/ServerSessionTimeoutListener.java   |   3 +-
 .../org/apache/sshd/AbstractSessionTest.java    |  21 +-
 .../org/apache/sshd/AuthenticationTest.java     |   3 -
 .../java/org/apache/sshd/KeyReExchangeTest.java | 148 ++++++++++++++
 .../test/java/org/apache/sshd/ServerTest.java   |   4 +-
 .../file/virtualfs/VirtualFileSystemTest.java   |  17 +-
 13 files changed, 479 insertions(+), 322 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8d226dd8/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java 
b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
index ce3e030..42af0ff 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
@@ -24,7 +24,6 @@ import java.security.KeyPair;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.TimeUnit;
 
 import org.apache.sshd.ClientChannel;
 import org.apache.sshd.ClientSession;
@@ -46,9 +45,9 @@ import org.apache.sshd.client.future.DefaultAuthFuture;
 import org.apache.sshd.client.scp.DefaultScpClient;
 import org.apache.sshd.client.sftp.DefaultSftpClient;
 import org.apache.sshd.common.KeyPairProvider;
-import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.Service;
 import org.apache.sshd.common.ServiceFactory;
+import org.apache.sshd.common.SessionListener;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.SshdSocketAddress;
@@ -69,15 +68,16 @@ public class ClientSessionImpl extends AbstractSession 
implements ClientSession
      */
     private Map<Object, Object> metadataMap = new HashMap<Object, Object>();
 
+    // TODO: clean service support a bit
+    private boolean initialServiceRequestSent;
     private ServiceFactory currentServiceFactory;
-
     private Service nextService;
     private ServiceFactory nextServiceFactory;
 
     protected AuthFuture authFuture;
 
     public ClientSessionImpl(ClientFactoryManager client, IoSession session) 
throws Exception {
-        super(client, session);
+        super(false, client, session);
         log.info("Session created...");
         // Need to set the initial service early as calling code likes to 
start trying to
         // manipulate it before the connection has even been established.  For 
instance, to
@@ -97,6 +97,7 @@ public class ClientSessionImpl extends AbstractSession 
implements ClientSession
         authFuture = new DefaultAuthFuture(lock);
         authFuture.setAuthed(false);
         sendClientIdentification();
+        kexState = KEX_STATE_INIT;
         sendKexInit();
     }
 
@@ -225,99 +226,7 @@ public class ClientSessionImpl extends AbstractSession 
implements ClientSession
 
     protected void handleMessage(Buffer buffer) throws Exception {
         synchronized (lock) {
-            doHandleMessage(buffer);
-        }
-    }
-
-    protected void doHandleMessage(Buffer buffer) throws Exception {
-        SshConstants.Message cmd = buffer.getCommand();
-        log.debug("Received packet {}", cmd);
-        switch (cmd) {
-            case SSH_MSG_DISCONNECT: {
-                int code = buffer.getInt();
-                String msg = buffer.getString();
-                log.info("Received SSH_MSG_DISCONNECT (reason={}, msg={})", 
code, msg);
-                close(true);
-                break;
-            }
-            case SSH_MSG_UNIMPLEMENTED: {
-                int code = buffer.getInt();
-                log.info("Received SSH_MSG_UNIMPLEMENTED #{}", code);
-                break;
-            }
-            case SSH_MSG_DEBUG: {
-                boolean display = buffer.getBoolean();
-                String msg = buffer.getString();
-                log.info("Received SSH_MSG_DEBUG (display={}) '{}'", display, 
msg);
-                break;
-            }
-            case SSH_MSG_IGNORE:
-                log.info("Received SSH_MSG_IGNORE");
-                break;
-            default:
-                switch (getState()) {
-                    case ReceiveKexInit:
-                        if (cmd != SshConstants.Message.SSH_MSG_KEXINIT) {
-                            log.error("Ignoring command {} while waiting for 
{}", cmd, SshConstants.Message.SSH_MSG_KEXINIT);
-                            break;
-                        }
-                        log.info("Received SSH_MSG_KEXINIT");
-                        receiveKexInit(buffer);
-                        negociate();
-                        kex = 
NamedFactory.Utils.create(factoryManager.getKeyExchangeFactories(), 
negociated[SshConstants.PROPOSAL_KEX_ALGS]);
-                        kex.init(this, serverVersion.getBytes(), 
clientVersion.getBytes(), I_S, I_C);
-                        setState(State.Kex);
-                        break;
-                    case Kex:
-                        buffer.rpos(buffer.rpos() - 1);
-                        if (kex.next(buffer)) {
-                            checkHost();
-                            sendNewKeys();
-                            setState(State.ReceiveNewKeys);
-                        }
-                        break;
-                    case ReceiveNewKeys:
-                        if (cmd != SshConstants.Message.SSH_MSG_NEWKEYS) {
-                            
disconnect(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR, "Protocol error: 
expected packet SSH_MSG_NEWKEYS, got " + cmd);
-                            return;
-                        }
-                        log.info("Received SSH_MSG_NEWKEYS");
-                        receiveNewKeys(false);
-                        log.info("Send SSH_MSG_SERVICE_REQUEST for {}", 
currentServiceFactory.getName());
-                        Buffer request = 
createBuffer(SshConstants.Message.SSH_MSG_SERVICE_REQUEST, 0);
-                        request.putString(currentServiceFactory.getName());
-                        writePacket(request);
-                        setState(State.ServiceRequestSent);
-                        // Assuming that MINA-SSHD only implements "explicit 
server authentication" it is permissible
-                        // for the client's service to start sending data 
before the service-accept has been received.
-                        // If "implicit authentication" were to ever be 
supported, then this would need to be
-                        // called after service-accept comes back.  See 
SSH-TRANSPORT.
-                        currentService.start();
-                        break;
-                    case ServiceRequestSent:
-                        if (cmd != 
SshConstants.Message.SSH_MSG_SERVICE_ACCEPT) {
-                            
disconnect(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR, "Protocol error: 
expected packet SSH_MSG_SERVICE_ACCEPT, got " + cmd);
-                            return;
-                        }
-                        log.info("Received SSH_MSG_SERVICE_ACCEPT");
-                        setState(State.Running);
-                        break;
-                    case Running:
-                        switch (cmd) {
-                            case SSH_MSG_REQUEST_SUCCESS:
-                                requestSuccess(buffer);
-                                break;
-                            case SSH_MSG_REQUEST_FAILURE:
-                                requestFailure(buffer);
-                                break;
-                            default:
-                                currentService.process(cmd, buffer);
-                                break;
-                        }
-                        break;
-                    default:
-                        throw new IllegalStateException("Unsupported state: " 
+ getState());
-                }
+            super.handleMessage(buffer);
         }
     }
 
@@ -362,13 +271,6 @@ public class ClientSessionImpl extends AbstractSession 
implements ClientSession
         }
     }
 
-    public void setState(State newState) {
-        synchronized (lock) {
-            super.setState(newState);
-            lock.notifyAll();
-        }
-    }
-
     protected boolean readIdentification(Buffer buffer) throws IOException {
         serverVersion = doReadIdentification(buffer);
         if (serverVersion == null) {
@@ -387,17 +289,18 @@ public class ClientSessionImpl extends AbstractSession 
implements ClientSession
         sendIdentification(clientVersion);
     }
 
-    private void sendKexInit() throws Exception {
+    protected void sendKexInit() throws IOException {
         clientProposal = createProposal(KeyPairProvider.SSH_RSA + "," + 
KeyPairProvider.SSH_DSS);
         I_C = sendKexInit(clientProposal);
     }
 
-    private void receiveKexInit(Buffer buffer) throws Exception {
+    protected void receiveKexInit(Buffer buffer) throws IOException {
         serverProposal = new String[SshConstants.PROPOSAL_MAX];
         I_S = receiveKexInit(buffer, serverProposal);
     }
 
-    private void checkHost() throws SshException {
+    @Override
+    protected void checkKeys() throws SshException {
         ServerKeyVerifier serverKeyVerifier = 
getClientFactoryManager().getServerKeyVerifier();
         SocketAddress remoteAddress = ioSession.getRemoteAddress();
 
@@ -406,7 +309,44 @@ public class ClientSessionImpl extends AbstractSession 
implements ClientSession
         }
     }
 
-       public Map<Object, Object> getMetadataMap() {
+    @Override
+    protected void sendEvent(SessionListener.Event event) throws IOException {
+        if (event == SessionListener.Event.KeyEstablished) {
+            sendInitialServiceRequest();
+        }
+        synchronized (lock) {
+            lock.notifyAll();
+        }
+        super.sendEvent(event);
+    }
+
+    protected void sendInitialServiceRequest() throws IOException {
+        if (initialServiceRequestSent) {
+            return;
+        }
+        initialServiceRequestSent = true;
+        log.info("Send SSH_MSG_SERVICE_REQUEST for {}", 
currentServiceFactory.getName());
+        Buffer request = 
createBuffer(SshConstants.Message.SSH_MSG_SERVICE_REQUEST, 0);
+        request.putString(currentServiceFactory.getName());
+        writePacket(request);
+        // Assuming that MINA-SSHD only implements "explicit server 
authentication" it is permissible
+        // for the client's service to start sending data before the 
service-accept has been received.
+        // If "implicit authentication" were to ever be supported, then this 
would need to be
+        // called after service-accept comes back.  See SSH-TRANSPORT.
+        currentService.start();
+    }
+
+    @Override
+    public void startService(String name) throws Exception {
+        throw new IllegalStateException("Starting services is not supported on 
the client side");
+    }
+
+    @Override
+    public void resetIdleTimeout() {
+
+    }
+
+    public Map<Object, Object> getMetadataMap() {
                return metadataMap;
        }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8d226dd8/sshd-core/src/main/java/org/apache/sshd/common/Session.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/Session.java 
b/sshd-core/src/main/java/org/apache/sshd/common/Session.java
index cc968e3..9b8bcae 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/Session.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/Session.java
@@ -20,6 +20,7 @@ package org.apache.sshd.common;
 
 import java.io.IOException;
 
+import org.apache.sshd.common.future.SshFuture;
 import org.apache.sshd.common.io.IoWriteFuture;
 import org.apache.sshd.common.util.Buffer;
 
@@ -30,17 +31,6 @@ import org.apache.sshd.common.util.Buffer;
  */
 public interface Session {
 
-    public enum State {
-        ReceiveKexInit, Kex, ReceiveNewKeys, ServiceRequestSent, 
WaitForServiceRequest, Running, Closed
-    }
-
-    /**
-     * Retrieve the state of this session.
-     * @return the session's state
-     * @see SessionListener
-     */
-    State getState();
-
     /**
      * Returns the value of the user-defined attribute of this session.
      *
@@ -153,6 +143,11 @@ public interface Session {
     void removeListener(SessionListener listener);
 
     /**
+     * Initiate a new key exchange.
+     */
+    SshFuture reExchangeKeys() throws IOException;
+
+    /**
      * Type safe key for storage within the user attributes of {@link 
org.apache.sshd.common.session.AbstractSession}.
      * Typically it is used as a static variable that is shared between the 
producer
      * and the consumer. To further restrict access the setting or getting it 
from

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8d226dd8/sshd-core/src/main/java/org/apache/sshd/common/SessionListener.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/SessionListener.java 
b/sshd-core/src/main/java/org/apache/sshd/common/SessionListener.java
index 96c96eb..dd30b48 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/SessionListener.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/SessionListener.java
@@ -25,6 +25,10 @@ package org.apache.sshd.common;
  */
 public interface SessionListener {
 
+    enum Event {
+        KeyEstablished, Authenticated
+    }
+
     /**
      * A new session just been created
      * @param session
@@ -32,11 +36,11 @@ public interface SessionListener {
     void sessionCreated(Session session);
 
     /**
-     * A session state has changed
-     * @param session
-     * @see org.apache.sshd.common.Session#getState()
+     * An event has been triggered
+     * @param sesssion
+     * @param event
      */
-    void sessionChanged(Session session);
+    void sessionEvent(Session sesssion, Event event);
 
     /**
      * A session has been closed

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8d226dd8/sshd-core/src/main/java/org/apache/sshd/common/SshConstants.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/SshConstants.java 
b/sshd-core/src/main/java/org/apache/sshd/common/SshConstants.java
index 241c61e..26148fd 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/SshConstants.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/SshConstants.java
@@ -97,6 +97,41 @@ public interface SshConstants {
         }
     }
 
+    static final int SSH_MSG_DISCONNECT=                      1;
+    static final int SSH_MSG_IGNORE=                          2;
+    static final int SSH_MSG_UNIMPLEMENTED=                   3;
+    static final int SSH_MSG_DEBUG=                           4;
+    static final int SSH_MSG_SERVICE_REQUEST=                 5;
+    static final int SSH_MSG_SERVICE_ACCEPT=                  6;
+    static final int SSH_MSG_KEXINIT=                        20;
+    static final int SSH_MSG_NEWKEYS=                        21;
+
+    static final int SSH_MSG_KEX_FIRST=                      30;
+    static final int SSH_MSG_KEX_LAST=                       49;
+
+    static final int SSH_MSG_KEXDH_INIT=                     30;
+    static final int SSH_MSG_KEXDH_REPLY=                    31;
+
+    static final int SSH_MSG_KEX_DH_GEX_GROUP=               31;
+    static final int SSH_MSG_KEX_DH_GEX_INIT=                32;
+    static final int SSH_MSG_KEX_DH_GEX_REPLY=               33;
+    static final int SSH_MSG_KEX_DH_GEX_REQUEST=             34;
+
+    static final int SSH_MSG_GLOBAL_REQUEST=                 80;
+    static final int SSH_MSG_REQUEST_SUCCESS=                81;
+    static final int SSH_MSG_REQUEST_FAILURE=                82;
+    static final int SSH_MSG_CHANNEL_OPEN=                   90;
+    static final int SSH_MSG_CHANNEL_OPEN_CONFIRMATION=      91;
+    static final int SSH_MSG_CHANNEL_OPEN_FAILURE=           92;
+    static final int SSH_MSG_CHANNEL_WINDOW_ADJUST=          93;
+    static final int SSH_MSG_CHANNEL_DATA=                   94;
+    static final int SSH_MSG_CHANNEL_EXTENDED_DATA=          95;
+    static final int SSH_MSG_CHANNEL_EOF=                    96;
+    static final int SSH_MSG_CHANNEL_CLOSE=                  97;
+    static final int SSH_MSG_CHANNEL_REQUEST=                98;
+    static final int SSH_MSG_CHANNEL_SUCCESS=                99;
+    static final int SSH_MSG_CHANNEL_FAILURE=               100;
+
     //
     // Values for the algorithms negociation 
     //

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8d226dd8/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractConnectionService.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractConnectionService.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractConnectionService.java
index 0149adb..ce68594 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractConnectionService.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractConnectionService.java
@@ -182,6 +182,12 @@ public abstract class AbstractConnectionService implements 
ConnectionService {
             case SSH_MSG_GLOBAL_REQUEST:
                 globalRequest(buffer);
                 break;
+            case SSH_MSG_REQUEST_SUCCESS:
+                requestSuccess(buffer);
+                break;
+            case SSH_MSG_REQUEST_FAILURE:
+                requestFailure(buffer);
+                break;
             default:
                 throw new IllegalStateException("Unsupported command: " + cmd);
         }
@@ -356,18 +362,21 @@ public abstract class AbstractConnectionService 
implements ConnectionService {
                         buf.putInt(channel.getLocalWindow().getSize());
                         buf.putInt(channel.getLocalWindow().getPacketSize());
                         session.writePacket(buf);
-                    } else if (future.getException() != null) {
-                        Buffer buf = 
session.createBuffer(SshConstants.Message.SSH_MSG_CHANNEL_OPEN_FAILURE, 0);
-                        buf.putInt(id);
-                        if (future.getException() instanceof 
OpenChannelException) {
-                            buf.putInt(((OpenChannelException) 
future.getException()).getReasonCode());
-                            buf.putString(future.getException().getMessage());
-                        } else {
-                            buf.putInt(0);
-                            buf.putString("Error opening channel: " + 
future.getException().getMessage());
+                    } else {
+                        Throwable exception = future.getException();
+                        if (exception != null) {
+                            Buffer buf = 
session.createBuffer(SshConstants.Message.SSH_MSG_CHANNEL_OPEN_FAILURE, 0);
+                            buf.putInt(id);
+                            if (exception instanceof OpenChannelException) {
+                                buf.putInt(((OpenChannelException) 
exception).getReasonCode());
+                                buf.putString(exception.getMessage());
+                            } else {
+                                buf.putInt(0);
+                                buf.putString("Error opening channel: " + 
exception.getMessage());
+                            }
+                            buf.putString("");
+                            session.writePacket(buf);
                         }
-                        buf.putString("");
-                        session.writePacket(buf);
                     }
                 } catch (IOException e) {
                     session.exceptionCaught(e);
@@ -421,4 +430,12 @@ public abstract class AbstractConnectionService implements 
ConnectionService {
         }
     }
 
+    protected void requestSuccess(Buffer buffer) throws Exception {
+        ((AbstractSession) session).requestSuccess(buffer);
+    }
+
+    protected void requestFailure(Buffer buffer) throws Exception {
+        ((AbstractSession) session).requestFailure(buffer);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8d226dd8/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java 
b/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java
index 942809e..b703dff 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java
@@ -24,6 +24,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.sshd.common.Cipher;
@@ -41,6 +42,8 @@ import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.future.CloseFuture;
 import org.apache.sshd.common.future.DefaultCloseFuture;
+import org.apache.sshd.common.future.DefaultSshFuture;
+import org.apache.sshd.common.future.SshFuture;
 import org.apache.sshd.common.future.SshFutureListener;
 import org.apache.sshd.common.io.IoCloseFuture;
 import org.apache.sshd.common.io.IoSession;
@@ -72,6 +75,14 @@ public abstract class AbstractSession implements Session {
      * and {@link #attachSession(IoSession, AbstractSession)}.
      */
     public static final String SESSION = "org.apache.sshd.session";
+
+    protected static final int KEX_STATE_INIT = 1;
+    protected static final int KEX_STATE_RUN =  2;
+    protected static final int KEX_STATE_KEYS = 3;
+    protected static final int KEX_STATE_DONE = 4;
+
+    /** Client or server side */
+    protected final boolean isServer;
     /** Our logger */
     protected final Logger log = LoggerFactory.getLogger(getClass());
     /** The factory manager used to retrieve factories of Ciphers, Macs and 
other objects */
@@ -94,7 +105,7 @@ public abstract class AbstractSession implements Session {
     protected String username;
 
     /** Session listener */
-    protected final List<SessionListener> listeners = new 
ArrayList<SessionListener>();
+    protected final List<SessionListener> listeners = new 
CopyOnWriteArrayList<SessionListener>();
 
     //
     // Key exchange support
@@ -108,6 +119,8 @@ public abstract class AbstractSession implements Session {
     protected byte[] I_C; // the payload of the client's SSH_MSG_KEXINIT
     protected byte[] I_S; // the payload of the factoryManager's 
SSH_MSG_KEXINIT
     protected KeyExchange kex;
+    protected int kexState;
+    protected DefaultSshFuture reexchangeFuture;
 
     //
     // SSH packets encoding / decoding support
@@ -135,15 +148,14 @@ public abstract class AbstractSession implements Session {
 
     protected Service currentService;
 
-    private volatile State state = State.ReceiveKexInit;
-
     /**
      * Create a new session.
      *
      * @param factoryManager the factory manager
      * @param ioSession the underlying MINA session
      */
-    public AbstractSession(FactoryManager factoryManager, IoSession ioSession) 
{
+    public AbstractSession(boolean isServer, FactoryManager factoryManager, 
IoSession ioSession) {
+        this.isServer = isServer;
         this.factoryManager = factoryManager;
         this.ioSession = ioSession;
         this.random = factoryManager.getRandomFactory().create();
@@ -191,18 +203,6 @@ public abstract class AbstractSession implements Session {
         ioSession.setAttribute(SESSION, session);
     }
 
-    public State getState() {
-        return state;
-    }
-
-    protected void setState(State state) {
-        this.state = state;
-        final ArrayList<SessionListener> l = new 
ArrayList<SessionListener>(listeners);
-        for (SessionListener sl : l) {
-            sl.sessionChanged(this);
-        }
-    }
-
     public String getServerVersion() {
         return serverVersion;
     }
@@ -242,9 +242,10 @@ public abstract class AbstractSession implements Session {
         return authed;
     }
 
-    public void setAuthenticated(String username) {
+    public void setAuthenticated(String username) throws IOException {
         this.authed = true;
         this.username = username;
+        sendEvent(SessionListener.Event.Authenticated);
     }
 
     /**
@@ -285,7 +286,102 @@ public abstract class AbstractSession implements Session {
      * @param buffer the buffer containing the packet
      * @throws Exception if an exeption occurs while handling this packet.
      */
-    protected abstract void handleMessage(Buffer buffer) throws Exception;
+    protected void handleMessage(Buffer buffer) throws Exception {
+        SshConstants.Message cmd = buffer.getCommand();
+        switch (cmd) {
+            case SSH_MSG_DISCONNECT: {
+                int code = buffer.getInt();
+                String msg = buffer.getString();
+                log.debug("Received SSH_MSG_DISCONNECT (reason={}, msg={})", 
code, msg);
+                close(true);
+                break;
+            }
+            case SSH_MSG_IGNORE: {
+                log.debug("Received SSH_MSG_IGNORE");
+                break;
+            }
+            case SSH_MSG_UNIMPLEMENTED: {
+                int code = buffer.getInt();
+                log.debug("Received SSH_MSG_UNIMPLEMENTED #{}", code);
+                break;
+            }
+            case SSH_MSG_DEBUG: {
+                boolean display = buffer.getBoolean();
+                String msg = buffer.getString();
+                log.debug("Received SSH_MSG_DEBUG (display={}) '{}'", display, 
msg);
+                break;
+            }
+            case SSH_MSG_SERVICE_REQUEST:
+                String service = buffer.getString();
+                log.debug("Received SSH_MSG_SERVICE_REQUEST '{}'", service);
+                if (kexState != KEX_STATE_DONE) {
+                    throw new IllegalStateException("Received command " + cmd 
+ " before key exchange is finished");
+                }
+                try {
+                    startService(service);
+                } catch (Exception e) {
+                    log.debug("Service " + service + " rejected", e);
+                    
disconnect(SshConstants.SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE, "Bad service 
request: " + service);
+                    break;
+                }
+                log.debug("Accepted service {}", service);
+                Buffer response = 
createBuffer(SshConstants.Message.SSH_MSG_SERVICE_ACCEPT, 0);
+                response.putString(service);
+                writePacket(response);
+                break;
+            case SSH_MSG_SERVICE_ACCEPT:
+                log.debug("Received SSH_MSG_SERVICE_ACCEPT");
+                if (kexState != KEX_STATE_DONE) {
+                    throw new IllegalStateException("Received command " + cmd 
+ " before key exchange is finished");
+                }
+                serviceAccept();
+                break;
+            case SSH_MSG_KEXINIT:
+                log.debug("Received SSH_MSG_KEXINIT");
+                receiveKexInit(buffer);
+                if (kexState == KEX_STATE_DONE) {
+                    sendKexInit();
+                } else if (kexState != KEX_STATE_INIT) {
+                    throw new IllegalStateException("Received SSH_MSG_KEXINIT 
while key exchange is running");
+                }
+                kexState = KEX_STATE_RUN;
+                negociate();
+                kex = 
NamedFactory.Utils.create(factoryManager.getKeyExchangeFactories(), 
negociated[SshConstants.PROPOSAL_KEX_ALGS]);
+                kex.init(this, serverVersion.getBytes(), 
clientVersion.getBytes(), I_S, I_C);
+                break;
+            case SSH_MSG_NEWKEYS:
+                log.debug("Received SSH_MSG_NEWKEYS");
+                if (kexState != KEX_STATE_KEYS) {
+                    throw new IllegalStateException("Received command " + cmd 
+ " before key exchange is finished");
+                }
+                receiveNewKeys();
+                kexState = KEX_STATE_DONE;
+                if (reexchangeFuture != null) {
+                    reexchangeFuture.setValue(true);
+                }
+                sendEvent(SessionListener.Event.KeyEstablished);
+                break;
+            default:
+                log.debug("Received {}", cmd);
+                if (cmd.toByte() >= SshConstants.SSH_MSG_KEX_FIRST && 
cmd.toByte() <= SshConstants.SSH_MSG_KEX_LAST) {
+                    if (kexState != KEX_STATE_RUN) {
+                        throw new IllegalStateException("Received kex command 
" + cmd.toByte() + " while not in key exchange");
+                    }
+                    buffer.rpos(buffer.rpos() - 1);
+                    if (kex.next(buffer)) {
+                        checkKeys();
+                        sendNewKeys();
+                        kexState = KEX_STATE_KEYS;
+                    }
+                } else if (currentService != null) {
+                    currentService.process(cmd, buffer);
+                    resetIdleTimeout();
+                } else {
+                    throw new IllegalStateException("Unsupported command " + 
cmd);
+                }
+                break;
+        }
+    }
 
     /**
      * Handle any exceptions that occured on this session.
@@ -331,8 +427,7 @@ public abstract class AbstractSession implements Session {
                     closeFuture.setClosed();
                     lock.notifyAll();
                 }
-                state = State.Closed;
-                log.info("SMessession {}@{} closed", s.getUsername(), 
s.getIoSession().getRemoteAddress());
+                log.info("Session {}@{} closed", s.getUsername(), 
s.getIoSession().getRemoteAddress());
                 // Fire 'close' event
                 final ArrayList<SessionListener> l = new 
ArrayList<SessionListener>(listeners);
                 for (SessionListener sl : l) {
@@ -373,12 +468,16 @@ public abstract class AbstractSession implements Session {
      * @throws java.io.IOException if an error occured when encoding sending 
the packet
      */
     public IoWriteFuture writePacket(Buffer buffer) throws IOException {
-        // Synchronize all write requests as needed by the encoding algorithm
-        // and also queue the write request in this synchronized block to 
ensure
-        // packets are sent in the correct order
-        synchronized (encodeLock) {
-            encode(buffer);
-            return ioSession.write(buffer);
+        try {
+            // Synchronize all write requests as needed by the encoding 
algorithm
+            // and also queue the write request in this synchronized block to 
ensure
+            // packets are sent in the correct order
+            synchronized (encodeLock) {
+                encode(buffer);
+                return ioSession.write(buffer);
+            }
+        } finally {
+            resetIdleTimeout();
         }
     }
 
@@ -768,10 +867,9 @@ public abstract class AbstractSession implements Session {
      * This method will intialize the ciphers, digests, macs and compression
      * according to the negociated server and client proposals.
      *
-     * @param isServer boolean indicating if this session is on the server or 
the client side
      * @throws Exception if an error occurs
      */
-    protected void receiveNewKeys(boolean isServer) throws Exception {
+    protected void receiveNewKeys() throws Exception {
         byte[] IVc2s;
         byte[] IVs2c;
         byte[] Ec2s;
@@ -1050,19 +1148,47 @@ public abstract class AbstractSession implements 
Session {
         if (listener == null) {
             throw new IllegalArgumentException();
         }
+        this.listeners.add(listener);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void removeListener(SessionListener listener) {
+        this.listeners.remove(listener);
+    }
 
-        synchronized (this.listeners) {
-            this.listeners.add(listener);
+    protected void sendEvent(SessionListener.Event event) throws IOException {
+        for (SessionListener sl : listeners) {
+            sl.sessionEvent(this, event);
         }
     }
 
+
     /**
      * {@inheritDoc}
      */
-    public void removeListener(SessionListener listener) {
-        synchronized (this.listeners) {
-            this.listeners.remove(listener);
+    public SshFuture reExchangeKeys() throws IOException {
+        if (kexState != KEX_STATE_DONE) {
+            throw new IllegalStateException("Can not perform key re-exchange 
while key exchange is already running");
         }
+        kexState = KEX_STATE_INIT;
+        sendKexInit();
+        reexchangeFuture = new DefaultSshFuture(null);
+        return reexchangeFuture;
     }
 
+    protected abstract void sendKexInit() throws IOException;
+
+    protected abstract void checkKeys() throws IOException;
+
+    protected abstract void receiveKexInit(Buffer buffer) throws IOException;
+
+    protected void serviceAccept() throws IOException {
+    }
+
+    public abstract void startService(String name) throws Exception;
+
+    public abstract void resetIdleTimeout();
+
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8d226dd8/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java 
b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java
index 9fc2a14..30c8311 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java
@@ -20,33 +20,16 @@ package org.apache.sshd.server.session;
 
 import java.io.IOException;
 import java.security.KeyPair;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.ScheduledExecutorService;
 
-import org.apache.sshd.SshServer;
-import org.apache.sshd.agent.common.AgentForwardSupport;
-import org.apache.sshd.agent.local.ChannelAgentForwarding;
-import org.apache.sshd.client.future.OpenFuture;
-import org.apache.sshd.common.Channel;
 import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.common.Service;
 import org.apache.sshd.common.ServiceFactory;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.SshException;
-import org.apache.sshd.common.SshdSocketAddress;
-import org.apache.sshd.common.future.CloseFuture;
-import org.apache.sshd.common.future.SshFutureListener;
 import org.apache.sshd.common.io.IoSession;
 import org.apache.sshd.common.io.IoWriteFuture;
 import org.apache.sshd.common.session.AbstractSession;
 import org.apache.sshd.common.util.Buffer;
 import org.apache.sshd.server.ServerFactoryManager;
-import org.apache.sshd.server.UserAuth;
-import org.apache.sshd.server.channel.OpenChannelException;
-import org.apache.sshd.server.x11.X11ForwardSupport;
 
 /**
  *
@@ -68,12 +51,13 @@ public class ServerSession extends AbstractSession {
     private int idleTimeoutMs = 10 * 60 * 1000; // 10 minutes in milliseconds
 
     public ServerSession(ServerFactoryManager server, IoSession ioSession) 
throws Exception {
-        super(server, ioSession);
+        super(true, server, ioSession);
         authTimeoutMs = getIntProperty(ServerFactoryManager.AUTH_TIMEOUT, 
authTimeoutMs);
         authTimeoutTimestamp = System.currentTimeMillis() + authTimeoutMs;
         idleTimeoutMs = getIntProperty(ServerFactoryManager.IDLE_TIMEOUT, 
idleTimeoutMs);
         log.info("Session created from {}", ioSession.getRemoteAddress());
         sendServerIdentification();
+        kexState = KEX_STATE_INIT;
         sendKexInit();
     }
 
@@ -85,135 +69,17 @@ public class ServerSession extends AbstractSession {
         return (ServerFactoryManager) factoryManager;
     }
 
-    protected ScheduledExecutorService getScheduledExecutorService() {
-        return getServerFactoryManager().getScheduledExecutorService();
-    }
-
-    @Override
-    public IoWriteFuture writePacket(Buffer buffer) throws IOException {
-        boolean rescheduleIdleTimer = getState() == State.Running;
-        if (rescheduleIdleTimer) {
-            resetIdleTimeout();
-        }
-        IoWriteFuture future = super.writePacket(buffer);
-        if (rescheduleIdleTimer) {
-            resetIdleTimeout();
-        }
-        return future;
-    }
-
-    protected void handleMessage(Buffer buffer) throws Exception {
-        SshConstants.Message cmd = buffer.getCommand();
-        log.debug("Received packet {}", cmd);
-        switch (cmd) {
-            case SSH_MSG_DISCONNECT: {
-                int code = buffer.getInt();
-                String msg = buffer.getString();
-                log.debug("Received SSH_MSG_DISCONNECT (reason={}, msg={})", 
code, msg);
-                close(true);
-                break;
-            }
-            case SSH_MSG_UNIMPLEMENTED: {
-                int code = buffer.getInt();
-                log.debug("Received SSH_MSG_UNIMPLEMENTED #{}", code);
-                break;
-            }
-            case SSH_MSG_DEBUG: {
-                boolean display = buffer.getBoolean();
-                String msg = buffer.getString();
-                log.debug("Received SSH_MSG_DEBUG (display={}) '{}'", display, 
msg);
-                break;
-            }
-            case SSH_MSG_IGNORE:
-                log.debug("Received SSH_MSG_IGNORE");
-                break;
-            default:
-                switch (getState()) {
-                    case ReceiveKexInit:
-                        if (cmd != SshConstants.Message.SSH_MSG_KEXINIT) {
-                            log.warn("Ignoring command " + cmd + " while 
waiting for " + SshConstants.Message.SSH_MSG_KEXINIT);
-                            break;
-                        }
-                        log.debug("Received SSH_MSG_KEXINIT");
-                        receiveKexInit(buffer);
-                        negociate();
-                        kex = 
NamedFactory.Utils.create(factoryManager.getKeyExchangeFactories(), 
negociated[SshConstants.PROPOSAL_KEX_ALGS]);
-                        kex.init(this, serverVersion.getBytes(), 
clientVersion.getBytes(), I_S, I_C);
-                        setState(State.Kex);
-                        break;
-                    case Kex:
-                        buffer.rpos(buffer.rpos() - 1);
-                        if (kex.next(buffer)) {
-                            sendNewKeys();
-                            setState(State.ReceiveNewKeys);
-                        }
-                        break;
-                    case ReceiveNewKeys:
-                        if (cmd != SshConstants.Message.SSH_MSG_NEWKEYS) {
-                            
disconnect(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR, "Protocol error: 
expected packet " + SshConstants.Message.SSH_MSG_NEWKEYS + ", got " + cmd);
-                            return;
-                        }
-                        log.debug("Received SSH_MSG_NEWKEYS");
-                        receiveNewKeys(true);
-                        setState(State.WaitForServiceRequest);
-                        break;
-                    case WaitForServiceRequest:
-                        if (cmd != 
SshConstants.Message.SSH_MSG_SERVICE_REQUEST) {
-                            log.debug("Expecting a {}, but received {}", 
SshConstants.Message.SSH_MSG_SERVICE_REQUEST, cmd);
-                            notImplemented();
-                        } else {
-                            String service = buffer.getString();
-                            log.debug("Received SSH_MSG_SERVICE_REQUEST '{}'", 
service);
-                            try {
-                                startService(service);
-                            } catch (Exception e) {
-                                log.debug("Service " + service + " rejected", 
e);
-                                
disconnect(SshConstants.SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE, "Bad service 
request: " + service);
-                                break;
-                            }
-                            log.debug("Accepted service {}", service);
-                            Buffer response = 
createBuffer(SshConstants.Message.SSH_MSG_SERVICE_ACCEPT, 0);
-                            response.putString(service);
-                            writePacket(response);
-                            setState(State.Running);
-                        }
-                        break;
-                    case Running:
-                        running(cmd, buffer);
-                        resetIdleTimeout();
-                        break;
-                    default:
-                        throw new IllegalStateException("Unsupported state: " 
+ getState());
-                }
-        }
+    protected void checkKeys() {
     }
 
     public void startService(String name) throws Exception {
         currentService = 
ServiceFactory.Utils.create(getFactoryManager().getServiceFactories(), name, 
this);
     }
 
-    private void running(SshConstants.Message cmd, Buffer buffer) throws 
Exception {
-        switch (cmd) {
-            case SSH_MSG_KEXINIT:
-                receiveKexInit(buffer);
-                sendKexInit();
-                negociate();
-                kex = 
NamedFactory.Utils.create(factoryManager.getKeyExchangeFactories(), 
negociated[SshConstants.PROPOSAL_KEX_ALGS]);
-                kex.init(this, serverVersion.getBytes(), 
clientVersion.getBytes(), I_S, I_C);
-                break;
-            case SSH_MSG_KEXDH_INIT:
-                buffer.rpos(buffer.rpos() - 1);
-                if (kex.next(buffer)) {
-                    sendNewKeys();
-                }
-                break;
-            case SSH_MSG_NEWKEYS:
-                receiveNewKeys(true);
-                break;
-            default:
-                currentService.process(cmd, buffer);
-                break;
-        }
+    @Override
+    protected void serviceAccept() throws IOException {
+        // TODO: can services be initiated by the server-side ?
+        disconnect(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR, "Unsupported 
packet: SSH_MSG_SERVICE_ACCEPT");
     }
 
     /**
@@ -223,7 +89,7 @@ public class ServerSession extends AbstractSession {
      * @throws IOException
      */
     protected void checkForTimeouts() throws IOException {
-        if (getState() != State.Closed) {
+        if (!closing) {
             long now = System.currentTimeMillis();
             if (!authed && now > authTimeoutTimestamp) {
                 disconnect(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR, 
"Session has timed out waiting for authentication after " + authTimeoutMs + " 
ms.");
@@ -247,7 +113,7 @@ public class ServerSession extends AbstractSession {
         sendIdentification(serverVersion);
     }
 
-    private void sendKexInit() throws IOException {
+    protected void sendKexInit() throws IOException {
         serverProposal = 
createProposal(factoryManager.getKeyPairProvider().getKeyTypes());
         I_S = sendKexInit(serverProposal);
     }
@@ -265,7 +131,7 @@ public class ServerSession extends AbstractSession {
         return true;
     }
 
-    private void receiveKexInit(Buffer buffer) throws IOException {
+    protected void receiveKexInit(Buffer buffer) throws IOException {
         clientProposal = new String[SshConstants.PROPOSAL_MAX];
         I_C = receiveKexInit(buffer, clientProposal);
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8d226dd8/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionTimeoutListener.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionTimeoutListener.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionTimeoutListener.java
index 580249c..d85315f 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionTimeoutListener.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionTimeoutListener.java
@@ -44,8 +44,7 @@ public class ServerSessionTimeoutListener implements 
SessionListener, Runnable {
         }
     }
 
-    public void sessionChanged(Session session) {
-        // ignore
+    public void sessionEvent(Session sesssion, Event event) {
     }
 
     public void sessionClosed(Session s) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8d226dd8/sshd-core/src/test/java/org/apache/sshd/AbstractSessionTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/AbstractSessionTest.java 
b/sshd-core/src/test/java/org/apache/sshd/AbstractSessionTest.java
index 1c4486a..8943be5 100644
--- a/sshd-core/src/test/java/org/apache/sshd/AbstractSessionTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/AbstractSessionTest.java
@@ -18,6 +18,8 @@
  */
 package org.apache.sshd;
 
+import java.io.IOException;
+
 import org.apache.mina.core.buffer.IoBuffer;
 import org.apache.sshd.common.session.AbstractSession;
 import org.apache.sshd.common.util.Buffer;
@@ -102,9 +104,7 @@ public class AbstractSessionTest {
 
     public static class MySession extends AbstractSession {
         public MySession() {
-            super(SshServer.setUpDefaultServer(), null);
-        }
-        public void messageReceived(IoBuffer byteBuffer) throws Exception {
+            super(true, SshServer.setUpDefaultServer(), null);
         }
         protected void handleMessage(Buffer buffer) throws Exception {
         }
@@ -114,5 +114,20 @@ public class AbstractSessionTest {
         public String doReadIdentification(Buffer buffer) {
             return super.doReadIdentification(buffer);
         }
+        @Override
+        protected void sendKexInit() throws IOException {
+        }
+        @Override
+        protected void checkKeys() {
+        }
+        @Override
+        protected void receiveKexInit(Buffer buffer) throws IOException {
+        }
+        @Override
+        public void startService(String name) throws Exception {
+        }
+        @Override
+        public void resetIdleTimeout() {
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8d226dd8/sshd-core/src/test/java/org/apache/sshd/AuthenticationTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/AuthenticationTest.java 
b/sshd-core/src/test/java/org/apache/sshd/AuthenticationTest.java
index bbbd863..1d429c3 100644
--- a/sshd-core/src/test/java/org/apache/sshd/AuthenticationTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/AuthenticationTest.java
@@ -136,9 +136,6 @@ public class AuthenticationTest {
         public TestSession(ServerFactoryManager server, IoSession ioSession) 
throws Exception {
             super(server, ioSession);
         }
-        public void setState(State state) {
-            super.setState(state);
-        }
         public void handleMessage(Buffer buffer) throws Exception {
             super.handleMessage(buffer);
         }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8d226dd8/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java 
b/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java
new file mode 100644
index 0000000..e6b1c54
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/KeyReExchangeTest.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.Collections;
+
+import com.jcraft.jsch.JSch;
+import org.apache.sshd.client.channel.ChannelShell;
+import org.apache.sshd.client.kex.DHG1;
+import org.apache.sshd.client.kex.DHG14;
+import org.apache.sshd.client.kex.DHGEX;
+import org.apache.sshd.client.kex.DHGEX256;
+import org.apache.sshd.client.kex.ECDHP256;
+import org.apache.sshd.client.kex.ECDHP384;
+import org.apache.sshd.client.kex.ECDHP521;
+import org.apache.sshd.common.KeyExchange;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.util.SecurityUtils;
+import org.apache.sshd.util.BogusPasswordAuthenticator;
+import org.apache.sshd.util.EchoShellFactory;
+import org.apache.sshd.util.JSchLogger;
+import org.apache.sshd.util.SimpleUserInfo;
+import org.apache.sshd.util.TeeOutputStream;
+import org.apache.sshd.util.Utils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test key exchange algorithms.
+ *
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class KeyReExchangeTest {
+
+    private SshServer sshd;
+    private int port;
+
+    @Before
+    public void setUp() throws Exception {
+        port = Utils.getFreePort();
+
+        sshd = SshServer.setUpDefaultServer();
+        sshd.setPort(port);
+        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+        sshd.setShellFactory(new EchoShellFactory());
+        sshd.setPasswordAuthenticator(new BogusPasswordAuthenticator());
+        sshd.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        sshd.stop();
+    }
+
+    @Test
+    public void testReExchangeFromClient() throws Exception {
+        JSchLogger.init();
+        JSch.setConfig("kex", "diffie-hellman-group-exchange-sha1");
+        JSch sch = new JSch();
+        com.jcraft.jsch.Session s = sch.getSession("smx", "localhost", port);
+        s.setUserInfo(new SimpleUserInfo("smx"));
+        s.connect();
+        com.jcraft.jsch.Channel c = s.openChannel("shell");
+        c.connect();
+        OutputStream os = c.getOutputStream();
+        InputStream is = c.getInputStream();
+        for (int i = 0; i < 10; i++) {
+            os.write("this is my command\n".getBytes());
+            os.flush();
+            byte[] data = new byte[512];
+            int len = is.read(data);
+            String str = new String(data, 0, len);
+            assertEquals("this is my command\n", str);
+            s.rekey();
+        }
+        c.disconnect();
+        s.disconnect();
+    }
+
+    @Test
+    public void testReExchangeFromNativeClient() throws Exception {
+        SshClient client = SshClient.setUpDefaultClient();
+        client.start();
+        ClientSession session = client.connect("localhost", 
port).await().getSession();
+        session.authPassword("smx", "smx").await();
+        ChannelShell channel = session.createShellChannel();
+
+        ByteArrayOutputStream sent = new ByteArrayOutputStream();
+        PipedOutputStream pipedIn = new PipedOutputStream();
+        channel.setIn(new PipedInputStream(pipedIn));
+        OutputStream teeOut = new TeeOutputStream(sent, pipedIn);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        ByteArrayOutputStream err = new ByteArrayOutputStream();
+        channel.setOut(out);
+        channel.setErr(err);
+        channel.open();
+
+        teeOut.write("this is my command\n".getBytes());
+        teeOut.flush();
+
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < 10; i++) {
+            sb.append("0123456789");
+        }
+        sb.append("\n");
+
+        for (int i = 0; i < 10; i++) {
+            teeOut.write(sb.toString().getBytes());
+            teeOut.flush();
+            session.reExchangeKeys().await();
+        }
+        teeOut.write("exit\n".getBytes());
+        teeOut.flush();
+
+        channel.waitFor(ClientChannel.CLOSED, 0);
+
+        channel.close(false);
+        client.stop();
+
+        assertArrayEquals(sent.toByteArray(), out.toByteArray());
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8d226dd8/sshd-core/src/test/java/org/apache/sshd/ServerTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/ServerTest.java 
b/sshd-core/src/test/java/org/apache/sshd/ServerTest.java
index d7fea87..6cd2a08 100644
--- a/sshd-core/src/test/java/org/apache/sshd/ServerTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/ServerTest.java
@@ -150,8 +150,8 @@ public class ServerTest {
             public void sessionCreated(Session session) {
                 System.out.println("Session created");
             }
-            public void sessionChanged(Session session) {
-                System.out.println("Session changed");
+            public void sessionEvent(Session sesssion, Event event) {
+                System.out.println("Session event: " + event);
             }
             public void sessionClosed(Session session) {
                 System.out.println("Session closed");

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8d226dd8/sshd-core/src/test/java/org/apache/sshd/common/file/virtualfs/VirtualFileSystemTest.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/common/file/virtualfs/VirtualFileSystemTest.java
 
b/sshd-core/src/test/java/org/apache/sshd/common/file/virtualfs/VirtualFileSystemTest.java
index 222fce0..c9e887e 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/common/file/virtualfs/VirtualFileSystemTest.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/common/file/virtualfs/VirtualFileSystemTest.java
@@ -77,7 +77,7 @@ public class VirtualFileSystemTest {
 
     static class TestSession extends AbstractSession {
         TestSession() {
-            super(SshServer.setUpDefaultServer(), null);
+            super(true, SshServer.setUpDefaultServer(), null);
             this.username = "userName";
         }
         @Override
@@ -87,6 +87,21 @@ public class VirtualFileSystemTest {
         protected boolean readIdentification(Buffer buffer) throws IOException 
{
             return false;
         }
+        @Override
+        protected void sendKexInit() throws IOException {
+        }
+        @Override
+        protected void checkKeys() {
+        }
+        @Override
+        protected void receiveKexInit(Buffer buffer) throws IOException {
+        }
+        @Override
+        public void startService(String name) throws Exception {
+        }
+        @Override
+        public void resetIdleTimeout() {
+        }
     }
 
     static class TestFactoryManager extends AbstractFactoryManager {

Reply via email to