Add delay on authentication failures

This provides some basic rate limiting that will make it difficult
for an attacker to brute force passwords. Only relevant when the
blacklist is disabled as otherwise the attacker only gets a very
limited number of attempts.
diff --git a/common/rfb/SConnection.cxx b/common/rfb/SConnection.cxx
index 690653a..4e224aa 100644
--- a/common/rfb/SConnection.cxx
+++ b/common/rfb/SConnection.cxx
@@ -236,11 +236,8 @@
     }
   } catch (AuthFailureException& e) {
     vlog.error("AuthFailureException: %s", e.str());
-    os->writeU32(secResultFailed);
-    if (!client.beforeVersion(3,8)) // 3.8 onwards have failure message
-      os->writeString(e.str());
-    os->flush();
-    throw;
+    state_ = RFBSTATE_SECURITY_FAILURE;
+    authFailure(e.str());
   }
 }
 
@@ -315,6 +312,19 @@
 {
 }
 
+void SConnection::authFailure(const char* reason)
+{
+  if (state_ != RFBSTATE_SECURITY_FAILURE)
+    throw Exception("SConnection::authFailure: invalid state");
+
+  os->writeU32(secResultFailed);
+  if (!client.beforeVersion(3,8)) // 3.8 onwards have failure message
+    os->writeString(reason);
+  os->flush();
+
+  throw AuthFailureException(reason);
+}
+
 void SConnection::queryConnection(const char* userName)
 {
   approveConnection(true);
diff --git a/common/rfb/SConnection.h b/common/rfb/SConnection.h
index 2630240..31d1cb2 100644
--- a/common/rfb/SConnection.h
+++ b/common/rfb/SConnection.h
@@ -92,6 +92,10 @@
     // authSuccess() is called when authentication has succeeded.
     virtual void authSuccess();
 
+    // authFailure() is called when authentication has failed. The default
+    // implementation will inform the client and throw a AuthFailureException.
+    virtual void authFailure(const char* reason);
+
     // queryConnection() is called when authentication has succeeded, but
     // before informing the client.  It can be overridden to query a local user
     // to accept the incoming connection, for example.  The userName argument
@@ -160,6 +164,7 @@
       RFBSTATE_PROTOCOL_VERSION,
       RFBSTATE_SECURITY_TYPE,
       RFBSTATE_SECURITY,
+      RFBSTATE_SECURITY_FAILURE,
       RFBSTATE_QUERYING,
       RFBSTATE_INITIALISATION,
       RFBSTATE_NORMAL,
diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx
index ea5c52a..fe00dab 100644
--- a/common/rfb/VNCSConnectionST.cxx
+++ b/common/rfb/VNCSConnectionST.cxx
@@ -52,7 +52,8 @@
     losslessTimer(this), server(server_),
     updateRenderedCursor(false), removeRenderedCursor(false),
     continuousUpdates(false), encodeManager(this), idleTimer(this),
-    pointerEventTime(0), clientHasCursor(false)
+    pointerEventTime(0), clientHasCursor(false),
+    authFailureTimer(this)
 {
   setStreams(&sock->inStream(), &sock->outStream());
   peerEndpoint.buf = sock->getPeerEndpoint();
@@ -151,6 +152,15 @@
     sock->cork(true);
 
     while (getInStream()->checkNoWait(1)) {
+      // Silently drop any data if we are currently delaying an
+      // authentication failure response as otherwise we would close
+      // the connection on unexpected data, and an attacker could use
+      // that to detect our delayed state.
+      if (state() == RFBSTATE_SECURITY_FAILURE) {
+        getInStream()->skip(1);
+        continue;
+      }
+
       if (pendingSyncFence) {
         syncFence = true;
         pendingSyncFence = false;
@@ -407,6 +417,14 @@
   updates.add_changed(server->getPixelBuffer()->getRect());
 }
 
+void VNCSConnectionST::authFailure(const char* reason)
+{
+  // Introduce a slight delay of the authentication failure response
+  // to make it difficult to brute force a password
+  authFailureMsg.replaceBuf(strDup(reason));
+  authFailureTimer.start(100);
+}
+
 void VNCSConnectionST::queryConnection(const char* userName)
 {
   server->queryConnection(this, userName);
@@ -745,6 +763,8 @@
     if ((t == &congestionTimer) ||
         (t == &losslessTimer))
       writeFramebufferUpdate();
+    else if (t == &authFailureTimer)
+      SConnection::authFailure(authFailureMsg.buf);
   } catch (rdr::Exception& e) {
     close(e.str());
   }
diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h
index 662d9f3..a9a8d3a 100644
--- a/common/rfb/VNCSConnectionST.h
+++ b/common/rfb/VNCSConnectionST.h
@@ -109,6 +109,7 @@
     // These methods are invoked as callbacks from processMsg()
 
     virtual void authSuccess();
+    virtual void authFailure(const char* reason);
     virtual void queryConnection(const char* userName);
     virtual void clientInit(bool shared);
     virtual void setPixelFormat(const PixelFormat& pf);
@@ -184,6 +185,9 @@
     Point pointerEventPos;
     bool clientHasCursor;
 
+    Timer authFailureTimer;
+    CharArray authFailureMsg;
+
     CharArray closeReason;
   };
 }