WinVNC merged with VNC 4.1.1 code.

git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/branches/merge-with-vnc-4.1.1@550 3789f03b-4d11-0410-bbf8-ca57d06f2519
diff --git a/winvnc/VNCServerWin32.cxx b/winvnc/VNCServerWin32.cxx
index 67b3ec5..8d681c2 100644
--- a/winvnc/VNCServerWin32.cxx
+++ b/winvnc/VNCServerWin32.cxx
@@ -1,5 +1,5 @@
-/* Copyright (C) 2002-2004 RealVNC Ltd.  All Rights Reserved.
- *    
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
@@ -21,8 +21,8 @@
 #include <winvnc/VNCServerWin32.h>
 #include <winvnc/resource.h>
 #include <winvnc/STrayIcon.h>
-
-#include <rfb_win32/Win32Util.h>
+#include <rfb_win32/ComputerName.h>
+#include <rfb_win32/CurrentUser.h>
 #include <rfb_win32/Service.h>
 #include <rfb/SSecurityFactoryStandard.h>
 #include <rfb/Hostname.h>
@@ -38,90 +38,28 @@
 
 const TCHAR* winvnc::VNCServerWin32::RegConfigPath = _T("Software\\TightVNC\\WinVNC4");
 
-const UINT VNCM_REG_CHANGED = WM_USER;
-const UINT VNCM_COMMAND = WM_USER + 1;
-
 
 static IntParameter http_port("HTTPPortNumber",
   "TCP/IP port on which the server will serve the Java applet VNC Viewer ", 5800);
 static IntParameter port_number("PortNumber",
   "TCP/IP port on which the server will accept connections", 5900);
 static StringParameter hosts("Hosts",
-  "Filter describing which hosts are allowed access to this server", "+");
-static VncAuthPasswdConfigParameter vncAuthPasswd;
+  "Filter describing which hosts are allowed access to this server", "+0.0.0.0/0.0.0.0");
 static BoolParameter localHost("LocalHost",
   "Only accept connections from via the local loop-back network interface", false);
-
-
-// -=- ManagedListener
-//     Wrapper class which simplifies the management of a listening socket
-//     on a specified port, attached to a SocketManager and SocketServer.
-//     Ensures that socket and filter are deleted and updated appropriately.
-
-class ManagedListener {
-public:
-  ManagedListener(win32::SocketManager* mgr, SocketServer* svr)
-    : sock(0), filter(0), port(0), manager(mgr),
-      server(svr), localOnly(0) {}
-  ~ManagedListener() {setPort(0);}
-  void setPort(int port, bool localOnly=false);
-  void setFilter(const char* filter);
-  TcpListener* sock;
-protected:
-  TcpFilter* filter;
-  win32::SocketManager* manager;
-  SocketServer* server;
-  int port;
-  bool localOnly;
-};
-
-// - If the port number/localHost setting has changed then tell the
-//   SocketManager to shutdown and delete it.  Also remove &
-//   delete the filter.  Then try to open a socket on the new port.
-void ManagedListener::setPort(int newPort, bool newLocalOnly) {
-  if ((port == newPort) && (localOnly == newLocalOnly) && sock) return;
-  if (sock) {
-    vlog.info("Closed TcpListener on port %d", port);
-    sock->setFilter(0);
-    delete filter;
-    manager->remListener(sock);
-    sock = 0;
-    filter = 0;
-  }
-  port = newPort;
-  localOnly = newLocalOnly;
-  if (port != 0) {
-    try {
-      sock = new TcpListener(port, localOnly);
-      vlog.info("Created TcpListener on port %d%s", port,
-                localOnly ? "(localhost)" : "(any)");
-    } catch (rdr::Exception& e) {
-      vlog.error("TcpListener on port %d failed (%s)", port, e.str());
-    }
-  }
-  if (sock)
-    manager->addListener(sock, server);
-}
-
-void ManagedListener::setFilter(const char* newFilter) {
-  if (!sock) return;
-  vlog.info("Updating TcpListener filter");
-  sock->setFilter(0);
-  delete filter;
-  filter = new TcpFilter(newFilter);
-  sock->setFilter(filter);
-}
+static BoolParameter queryOnlyIfLoggedOn("QueryOnlyIfLoggedOn",
+  "Only prompt for a local user to accept incoming connections if there is a user logged on", false);
 
 
 VNCServerWin32::VNCServerWin32()
-  : vncServer(CStr(ComputerName().buf), &desktop),
-    httpServer(0), runServer(false),
-    isDesktopStarted(false),
-    command(NoCommand), commandSig(commandLock),
-    queryConnectDialog(0) {
-  // Create the Java-viewer HTTP server
-  httpServer = new JavaViewerServer(&vncServer);
-
+  : command(NoCommand), commandSig(commandLock),
+    commandEvent(CreateEvent(0, TRUE, FALSE, 0)),
+    vncServer(CStr(ComputerName().buf), &desktop),
+    hostThread(0), runServer(false), isDesktopStarted(false),
+    httpServer(&vncServer), config(&sockMgr), trayIcon(0),
+    rfbSock(&sockMgr), httpSock(&sockMgr),
+    queryConnectDialog(0)
+{
   // Initialise the desktop
   desktop.setStatusLocation(&isDesktopStarted);
 
@@ -130,14 +68,78 @@
 
   // Register the desktop's event to be handled
   sockMgr.addEvent(desktop.getUpdateEvent(), &desktop);
+
+  // Register the queued command event to be handled
+  sockMgr.addEvent(commandEvent, this);
 }
 
 VNCServerWin32::~VNCServerWin32() {
+  delete trayIcon;
+
   // Stop the SDisplay from updating our state
   desktop.setStatusLocation(0);
 
-  // Destroy the HTTP server
-  delete httpServer;
+  // Join the Accept/Reject dialog thread
+  if (queryConnectDialog)
+    delete queryConnectDialog->join();
+}
+
+
+void VNCServerWin32::processAddressChange(network::SocketListener* sock_) {
+  if (!trayIcon || (sock_ != rfbSock.sock))
+    return;
+
+  // Tool-tip prefix depends on server mode
+  const TCHAR* prefix = _T("VNC Server (User):");
+  if (isServiceProcess())
+    prefix = _T("VNC Server (Service):");
+
+  // Fetch the list of addresses
+  std::list<char*> addrs;
+  if (rfbSock.sock)
+    rfbSock.sock->getMyAddresses(&addrs);
+  else
+    addrs.push_front(strDup("Not accepting connections"));
+
+  // Allocate space for the new tip
+  std::list<char*>::iterator i, next_i;
+  int length = _tcslen(prefix)+1;
+  for (i=addrs.begin(); i!= addrs.end(); i++)
+    length += strlen(*i) + 1;
+
+  // Build the new tip
+  TCharArray toolTip(length);
+  _tcscpy(toolTip.buf, prefix);
+  for (i=addrs.begin(); i!= addrs.end(); i=next_i) {
+    next_i = i; next_i ++;
+    TCharArray addr = *i;    // Assumes ownership of string
+    _tcscat(toolTip.buf, addr.buf);
+    if (next_i != addrs.end())
+      _tcscat(toolTip.buf, _T(","));
+  }
+  
+  // Pass the new tip to the tray icon
+  vlog.info("Refreshing tray icon");
+  trayIcon->setToolTip(toolTip.buf);
+}
+
+void VNCServerWin32::regConfigChanged() {
+  // -=- Make sure we're listening on the right ports.
+  rfbSock.setServer(&vncServer);
+  rfbSock.setPort(port_number, localHost);
+  httpSock.setServer(&httpServer);
+  httpSock.setPort(http_port, localHost);
+
+  // -=- Update the Java viewer's web page port number.
+  httpServer.setRFBport(rfbSock.sock ? port_number : 0);
+
+  // -=- Update the TCP address filter for both ports, if open.
+  CharArray pattern(hosts.getData());
+  rfbSock.setFilter(pattern.buf);
+  httpSock.setFilter(pattern.buf);
+
+  // -=- Update the tray icon tooltip text with IP addresses
+  processAddressChange(rfbSock.sock);
 }
 
 
@@ -147,85 +149,37 @@
     runServer = true;
   }
 
+  // - Create the tray icon (if possible)
+  trayIcon = new STrayIconThread(*this, IDI_ICON, IDI_CONNECTED,
+                                 IDI_ICON_DISABLE, IDI_CONNECTED_DISABLE,
+                                 IDR_TRAY);
+
   // - Register for notification of configuration changes
+  config.setCallback(this);
   if (isServiceProcess())
     config.setKey(HKEY_LOCAL_MACHINE, RegConfigPath);
   else
     config.setKey(HKEY_CURRENT_USER, RegConfigPath);
-  config.setNotifyThread(Thread::self(), VNCM_REG_CHANGED);
 
-  // - Create the tray icon if possible
-  STrayIconThread trayIcon(*this, IDI_ICON, IDI_CONNECTED, IDI_ICON_DISABLE,
-                            IDI_CONNECTED_DISABLE, IDR_TRAY);
+  // - Set the address-changed handler for the RFB socket
+  rfbSock.setAddressChangeNotifier(this);
 
   DWORD result = 0;
   try {
-    // - Create some managed listening sockets
-    ManagedListener rfb(&sockMgr, &vncServer);
-    ManagedListener http(&sockMgr, httpServer);
+    vlog.debug("Entering message loop");
 
-    // - Continue to operate until WM_QUIT is processed
+    // - Run the server until we're told to quit
     MSG msg;
-    do {
-      // -=- Make sure we're listening on the right ports.
-      rfb.setPort(port_number, localHost);
-      http.setPort(http_port, localHost);
-
-      // -=- Update the Java viewer's web page port number.
-      httpServer->setRFBport(rfb.sock ? port_number : 0);
-
-      // -=- Update the TCP address filter for both ports, if open.
-      CharArray pattern;
-      pattern.buf = hosts.getData();
-      if (!localHost) {
-        rfb.setFilter(pattern.buf);
-        http.setFilter(pattern.buf);
-      }
-
-      // - If there is a listening port then add the address to the
-      //   tray icon's tool-tip text.
-      {
-        const TCHAR* prefix = isServiceProcess() ?
-          _T("VNC Server (Service):") : _T("VNC Server (User):");
-
-        std::list<char*> addrs;
-        if (rfb.sock)
-          rfb.sock->getMyAddresses(&addrs);
-        else
-          addrs.push_front(strDup("Not accepting connections"));
-
-        std::list<char*>::iterator i, next_i;
-        int length = _tcslen(prefix)+1;
-        for (i=addrs.begin(); i!= addrs.end(); i++)
-          length += strlen(*i) + 1;
-
-        TCharArray toolTip(length);
-        _tcscpy(toolTip.buf, prefix);
-        for (i=addrs.begin(); i!= addrs.end(); i=next_i) {
-          next_i = i; next_i ++;
-          TCharArray addr = *i;    // Assumes ownership of string
-          _tcscat(toolTip.buf, addr.buf);
-          if (next_i != addrs.end())
-            _tcscat(toolTip.buf, _T(","));
-        }
-        trayIcon.setToolTip(toolTip.buf);
-      }
-
-      vlog.debug("Entering message loop");
-
-      // - Run the server until the registry changes, or we're told to quit
-      while (sockMgr.getMessage(&msg, NULL, 0, 0)) {
-        if (msg.hwnd == 0) {
-          if (msg.message == VNCM_REG_CHANGED)
-            break;
-          if (msg.message == VNCM_COMMAND)
-            doCommand();
-        }
-        TranslateMessage(&msg);
-        DispatchMessage(&msg);
-      }
-
-    } while ((msg.message != WM_QUIT) || runServer);
+    int result = 0;
+    while (runServer) {
+      result = sockMgr.getMessage(&msg, NULL, 0, 0);
+      if (result < 0)
+        throw rdr::SystemException("getMessage", GetLastError());
+      if (!isServiceProcess() && (result == 0))
+        break;
+      TranslateMessage(&msg);
+      DispatchMessage(&msg);
+    }
 
     vlog.debug("Server exited cleanly");
   } catch (rdr::SystemException &s) {
@@ -246,7 +200,8 @@
 void VNCServerWin32::stop() {
   Lock l(runLock);
   runServer = false;
-  PostThreadMessage(hostThread->getThreadId(), WM_QUIT, 0, 0);
+  if (hostThread)
+    PostThreadMessage(hostThread->getThreadId(), WM_QUIT, 0, 0);
 }
 
 
@@ -283,6 +238,8 @@
                                             const char* userName,
                                             char** reason)
 {
+  if (queryOnlyIfLoggedOn && CurrentUserToken().noUserLoggedOn())
+    return VNCServerST::ACCEPT;
   if (queryConnectDialog) {
     *reason = rfb::strDup("Another connection is currently being queried.");
     return VNCServerST::REJECT;
@@ -293,51 +250,49 @@
 }
 
 void VNCServerWin32::queryConnectionComplete() {
-  Thread* qcd = queryConnectDialog;
-  queueCommand(QueryConnectionComplete, 0, 0);
-  delete qcd->join();
+  queueCommand(QueryConnectionComplete, 0, 0, false);
 }
 
 
-bool VNCServerWin32::queueCommand(Command cmd, const void* data, int len) {
+bool VNCServerWin32::queueCommand(Command cmd, const void* data, int len, bool wait) {
   Lock l(commandLock);
-  while (command != NoCommand) commandSig.wait();
+  while (command != NoCommand)
+    commandSig.wait();
   command = cmd;
   commandData = data;
   commandDataLen = len;
-  if (PostThreadMessage(hostThread->getThreadId(), VNCM_COMMAND, 0, 0))
-    while (command != NoCommand) commandSig.wait();
-  else
-    return false;
+  SetEvent(commandEvent);
+  if (wait) {
+    while (command != NoCommand)
+      commandSig.wait();
+    commandSig.signal();
+  }
   return true;
 }
 
-void VNCServerWin32::doCommand() {
-  Lock l(commandLock);
-  if (command == NoCommand) return;
+void VNCServerWin32::processEvent(HANDLE event_) {
+  ResetEvent(event_);
 
-  // Perform the required command
-  switch (command) {
+  if (event_ == commandEvent.h) {
+    // If there is no command queued then return immediately
+    {
+      Lock l(commandLock);
+      if (command == NoCommand)
+        return;
+    }
 
-  case DisconnectClients:
-    // Disconnect all currently active VNC Viewers
-    vncServer.closeClients((const char*)commandData);
-    break;
+    // Perform the required command
+    switch (command) {
 
-  case AddClient:
-    // Make a reverse connection to a VNC Viewer
-    vncServer.addClient((network::Socket*)commandData, true);
-    sockMgr.addSocket((network::Socket*)commandData, &vncServer);
-    break;
+    case DisconnectClients:
+      // Disconnect all currently active VNC Viewers
+      vncServer.closeClients((const char*)commandData);
+      break;
 
-  case QueryConnectionComplete:
-    // The Accept/Reject dialog has completed
-    // Get the result, then clean it up
-    vncServer.approveConnection(queryConnectDialog->getSock(),
-                                queryConnectDialog->isAccepted(),
-                                "Connection rejected by user");
-    queryConnectDialog = 0;
-    break;
+    case AddClient:
+      // Make a reverse connection to a VNC Viewer
+      sockMgr.addSocket((network::Socket*)commandData, &vncServer);
+      break;
   case GetClientsInfo:
     vncServer.getConnInfo((ListConnInfo*)commandData); 
     break;
@@ -345,11 +300,26 @@
     vncServer.setConnStatus((ListConnInfo*)commandData); 
     break;
 
-  default:
-    vlog.error("unknown command %d queued", command);
-  };
+    case QueryConnectionComplete:
+      // The Accept/Reject dialog has completed
+      // Get the result, then clean it up
+      vncServer.approveConnection(queryConnectDialog->getSock(),
+                                  queryConnectDialog->isAccepted(),
+                                  "Connection rejected by user");
+      delete queryConnectDialog->join();
+      queryConnectDialog = 0;
+      break;
 
-  // Clear the command and signal completion
-  command = NoCommand;
-  commandSig.signal();
+    default:
+      vlog.error("unknown command %d queued", command);
+    };
+
+    // Clear the command and signal completion
+    {
+      Lock l(commandLock);
+      command = NoCommand;
+      commandSig.signal();
+    }
+  }
 }
+