diff --git a/history.txt b/history.txt
index 6bba0801f40264bcd1604427b7ff6dca60137f74..ebf0add0be39fabac387b1ba712e4c7350832069 100644
--- a/history.txt
+++ b/history.txt
@@ -1,6 +1,11 @@
+2014-10-16 zzz
+ * NTCP: Deadlock fix 2nd try (ticket #1394)
+
 2014-10-15 zzz
  * Console, I2CP, i2ptunnel, SSLEepGet: Set allowed SSL
-   protocols and ciphers
+   protocols and ciphers. Disable SSLv3 and older ciphers.
+   Enable TLSv1.1 and TLSv1.2 on Java 7,
+   where it is disabled by default client-side.
 
 2014-10-14 zzz
  * I2NP: Implement DatabaseLookupMessage search type field
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index 5a58b19bc22ea9b489b5610aade18177efc4505e..e36cd3b14352a16a1e79c5fea474cb2f20cbaa5c 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -18,7 +18,7 @@ public class RouterVersion {
     /** deprecated */
     public final static String ID = "Monotone";
     public final static String VERSION = CoreVersion.VERSION;
-    public final static long BUILD = 11;
+    public final static long BUILD = 12;
 
     /** for example "-test" */
     public final static String EXTRA = "";
diff --git a/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java b/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java
index 4697e15f6d83a7a4ac9074c191c8591bd6f33caf..97369ed8a68622b2f4c4219518c22dfd7f8e48db 100644
--- a/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java
+++ b/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java
@@ -121,6 +121,7 @@ class EstablishState {
     private static final int HXY_SIZE = 32;  //Hash.HASH_LENGTH;
     private static final int HXY_TSB_PAD_SIZE = HXY_SIZE + 4 + 12;  // 48
 
+    private static final Object _stateLock = new Object();
     protected State _state;
 
     private enum State {
@@ -193,6 +194,13 @@ class EstablishState {
         _curDecrypted = SimpleByteCache.acquire(AES_SIZE);
     }
 
+    /** @since 0.9.16 */
+    private void changeState(State state) {
+        synchronized (_stateLock) {
+            _state = state;
+        }
+    }
+
     /**
      * parse the contents of the buffer as part of the handshake.  if the
      * handshake is completed and there is more data remaining, the data are
@@ -203,8 +211,10 @@ class EstablishState {
      * will return it to the pool.
      */
     public synchronized void receive(ByteBuffer src) {
-        if (_state == State.VERIFIED || _state == State.CORRUPT)
-            throw new IllegalStateException(prefix() + "received unexpected data on " + _con);
+        synchronized(_stateLock) {    
+            if (_state == State.VERIFIED || _state == State.CORRUPT)
+                throw new IllegalStateException(prefix() + "received unexpected data on " + _con);
+        }
         if (!src.hasRemaining())
             return; // nothing to receive
 
@@ -229,6 +239,9 @@ class EstablishState {
      *  will return it to the pool.
      *
      *  Caller must synch.
+     *
+     *  FIXME none of the _state comparisons use _stateLock, but whole thing
+     *  is synchronized, should be OK. See isComplete()
      */
     private void receiveInbound(ByteBuffer src) {
         while (_state == State.IB_INIT && src.hasRemaining()) {
@@ -243,7 +256,7 @@ class EstablishState {
             //    }
             //}
             if (_received >= XY_SIZE)
-                _state = State.IB_GOT_X;
+                changeState(State.IB_GOT_X);
         }
         while (_state == State.IB_GOT_X && src.hasRemaining()) {
             int i = _received - XY_SIZE;
@@ -252,7 +265,7 @@ class EstablishState {
             _hX_xor_bobIdentHash[i] = c;
             //if (_log.shouldLog(Log.DEBUG)) _log.debug("recv bih" + (int)c + " received=" + _received);
             if (i >= HXY_SIZE - 1)
-                _state = State.IB_GOT_HX;
+                changeState(State.IB_GOT_HX);
         }
 
         if (_state == State.IB_GOT_HX) {
@@ -325,7 +338,7 @@ class EstablishState {
                     System.arraycopy(_e_hXY_tsB, 0, write, XY_SIZE, HXY_TSB_PAD_SIZE);
 
                     // ok, now that is prepared, we want to actually send it, so make sure we are up for writing
-                    _state = State.IB_SENT_Y;
+                    changeState(State.IB_SENT_Y);
                     _transport.getPumper().wantsWrite(_con, write);
                     if (!src.hasRemaining()) return;
                 } catch (DHSessionKeyBuilder.InvalidPublicParameterException e) {
@@ -370,7 +383,7 @@ class EstablishState {
                         if (_log.shouldLog(Log.DEBUG))
                             _log.debug(prefix() + "got the RI size: " + sz);
                         _aliceIdentSize  = sz;
-                        _state = State.IB_GOT_RI_SIZE;
+                        changeState(State.IB_GOT_RI_SIZE);
 
                         // We must defer the calculations for total size of the message until
                         //  we get the full alice ident so
@@ -401,7 +414,7 @@ class EstablishState {
                             fail("Unsupported sig type");
                             return;
                         }
-                        _state = State.IB_GOT_RI;
+                        changeState(State.IB_GOT_RI);
                         // handle variable signature size
                         _sz_aliceIdent_tsA_padding_aliceSigSize = 2 + _aliceIdentSize + 4 + type.getSigLen();
                         int rem = (_sz_aliceIdent_tsA_padding_aliceSigSize % AES_SIZE);
@@ -453,6 +466,9 @@ class EstablishState {
      *  will return it to the pool.
      *
      *  Caller must synch.
+     *
+     *  FIXME none of the _state comparisons use _stateLock, but whole thing
+     *  is synchronized, should be OK. See isComplete()
      */
     private void receiveOutbound(ByteBuffer src) {
         // recv Y+E(H(X+Y)+tsB, sk, Y[239:255])
@@ -466,7 +482,7 @@ class EstablishState {
                     _dh.getSessionKey(); // force the calc
                     if (_log.shouldLog(Log.DEBUG))
                         _log.debug(prefix()+"DH session key calculated (" + _dh.getSessionKey().toBase64() + ")");
-                    _state = State.OB_GOT_Y;
+                    changeState(State.OB_GOT_Y);
                 } catch (DHSessionKeyBuilder.InvalidPublicParameterException e) {
                     _context.statManager().addRateData("ntcp.invalidDH", 1);
                     fail("Invalid X", e);
@@ -500,7 +516,7 @@ class EstablishState {
                     return;
                 }
                 SimpleByteCache.release(h);
-                _state = State.OB_GOT_HXY;
+                changeState(State.OB_GOT_HXY);
                 _tsB = DataHelper.fromLong(hXY_tsB, HXY_SIZE, 4); // their (Bob's) timestamp in seconds
                 _tsA = (_context.clock().now() + 500) / 1000; // our (Alice's) timestamp in seconds
                 if (_log.shouldLog(Log.DEBUG))
@@ -573,7 +589,7 @@ class EstablishState {
                     //_log.debug(prefix() + "encrypted response to Bob: " + Base64.encode(_prevEncrypted));
                 //}
                 // send 'er off (when the bw limiter says, etc)
-                _state = State.OB_SENT_RI;
+                changeState(State.OB_SENT_RI);
                 _transport.getPumper().wantsWrite(_con, _prevEncrypted);
             }
         }
@@ -607,7 +623,7 @@ class EstablishState {
                 _received++;
 
                 if (off >= _e_bobSig.length) {
-                    _state = State.OB_GOT_SIG;
+                    changeState(State.OB_GOT_SIG);
                     //if (_log.shouldLog(Log.DEBUG))
                     //    _log.debug(prefix() + "received E(S(X+Y+Alice.identHash+tsA+tsB)+padding, sk, prev): " + Base64.encode(_e_bobSig));
                     byte bobSig[] = new byte[_e_bobSig.length];
@@ -634,7 +650,6 @@ class EstablishState {
                         _context.statManager().addRateData("ntcp.invalidSignature", 1);
                         fail("Signature was invalid - attempt to spoof " + _con.getRemotePeer().calculateHash().toBase64() + "?");
                     } else {
-                        _state = State.VERIFIED;
                         if (_log.shouldLog(Log.DEBUG))
                             _log.debug(prefix() + "signature verified from Bob.  done!");
                         prepareExtra(src);
@@ -647,6 +662,7 @@ class EstablishState {
                         InetAddress ia = _con.getChannel().socket().getInetAddress();
                         if (ia != null)
                             _transport.setIP(_con.getRemotePeer().calculateHash(), ia.getAddress());
+                        changeState(State.VERIFIED);
                     }
                     return;
                 }
@@ -655,10 +671,24 @@ class EstablishState {
     }
 
     /** did the handshake fail for some reason? */
-    public synchronized boolean isCorrupt() { return _state == State.CORRUPT; }
+    public boolean isCorrupt() {
+        synchronized(_stateLock) {
+            return _state == State.CORRUPT;
+        }
+    }
 
-    /** @return is the handshake complete and valid? */
-    public synchronized boolean isComplete() { return _state == State.VERIFIED; }
+    /**
+     *  If synchronized on this, fails with
+     *  deadlocks from all over via CSFI.isEstablished().
+     *  Also CSFI.getFramedAveragePeerClockSkew().
+     *
+     *  @return is the handshake complete and valid?
+     */
+    public boolean isComplete() {
+        synchronized(_stateLock) {
+            return _state == State.VERIFIED;
+        }
+    }
 
     /**
      * We are Alice.
@@ -667,13 +697,17 @@ class EstablishState {
      * This method sends message #1 to Bob.
      */
     public synchronized void prepareOutbound() {
-        if (_state == State.OB_INIT) {
+        boolean shouldSend;
+        synchronized(_stateLock) {    
+            shouldSend = _state == State.OB_INIT;
+        }
+        if (shouldSend) {
             if (_log.shouldLog(Log.DEBUG))
                 _log.debug(prefix() + "send X");
             byte toWrite[] = new byte[XY_SIZE + _hX_xor_bobIdentHash.length];
             System.arraycopy(_X, 0, toWrite, 0, XY_SIZE);
             System.arraycopy(_hX_xor_bobIdentHash, 0, toWrite, XY_SIZE, _hX_xor_bobIdentHash.length);
-            _state = State.OB_SENT_X;
+            changeState(State.OB_SENT_X);
             _transport.getPumper().wantsWrite(_con, toWrite);
         } else {
             if (_log.shouldLog(Log.WARN))
@@ -815,7 +849,6 @@ class EstablishState {
                     _log.debug(prefix()+"Clock skew: " + diff + " ms");
                 }
 
-                _state = State.VERIFIED;
                 sendInboundConfirm(_aliceIdent, tsA);
                 _con.setRemotePeer(_aliceIdent);
                 if (_log.shouldLog(Log.DEBUG))
@@ -827,6 +860,7 @@ class EstablishState {
                 releaseBufs();
                 if (_log.shouldLog(Log.INFO))
                     _log.info(prefix()+"Verified remote peer as " + _aliceIdent.calculateHash());
+                changeState(State.VERIFIED);
             } else {
                 _context.statManager().addRateData("ntcp.invalidInboundSignature", 1);
                 fail("Peer verification failed - spoof of " + _aliceIdent.calculateHash() + "?");
@@ -917,9 +951,11 @@ class EstablishState {
 
     /** Caller must synch. */
     private void fail(String reason, Exception e, boolean bySkew) {
-        if (_state == State.CORRUPT || _state == State.VERIFIED)
-            return;
-        _state = State.CORRUPT;
+        synchronized(_stateLock) {    
+            if (_state == State.CORRUPT || _state == State.VERIFIED)
+                return;
+            changeState(State.CORRUPT);
+        }
         _failedBySkew = bySkew;
         _err = reason;
         _e = e;
@@ -938,8 +974,10 @@ class EstablishState {
             SimpleByteCache.release(_prevEncrypted);
         // Do not release _curEncrypted if verified, it is passed to
         // NTCPConnection to use as the IV
-        if (_state != State.VERIFIED)
-            SimpleByteCache.release(_curEncrypted);
+        synchronized(_stateLock) {    
+            if (_state != State.VERIFIED)
+                SimpleByteCache.release(_curEncrypted);
+        }
         SimpleByteCache.release(_curDecrypted);
         SimpleByteCache.release(_hX_xor_bobIdentHash);
         if (_dh.getPeerPublicValue() == null)