Initial revision


git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@2 3789f03b-4d11-0410-bbf8-ca57d06f2519
diff --git a/winvnc/VNCServerWin32.cxx b/winvnc/VNCServerWin32.cxx
new file mode 100644
index 0000000..a870cb1
--- /dev/null
+++ b/winvnc/VNCServerWin32.cxx
@@ -0,0 +1,341 @@
+/* Copyright (C) 2002-2004 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
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- WinVNC Version 4.0 Main Routine
+
+#include <winvnc/VNCServerWin32.h>
+#include <winvnc/resource.h>
+#include <winvnc/STrayIcon.h>
+
+#include <rfb_win32/Win32Util.h>
+#include <rfb_win32/Service.h>
+#include <rfb/SSecurityFactoryStandard.h>
+#include <rfb/Hostname.h>
+#include <rfb/LogWriter.h>
+
+using namespace rfb;
+using namespace win32;
+using namespace winvnc;
+using namespace network;
+
+static LogWriter vlog("VNCServerWin32");
+
+
+const TCHAR* winvnc::VNCServerWin32::RegConfigPath = _T("Software\\RealVNC\\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;
+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);
+}
+
+
+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);
+
+  // Initialise the desktop
+  desktop.setStatusLocation(&isDesktopStarted);
+
+  // Initialise the VNC server
+  vncServer.setQueryConnectionHandler(this);
+
+  // Register the desktop's event to be handled
+  sockMgr.addEvent(desktop.getUpdateEvent(), &desktop);
+}
+
+VNCServerWin32::~VNCServerWin32() {
+  // Stop the SDisplay from updating our state
+  desktop.setStatusLocation(0);
+
+  // Destroy the HTTP server
+  delete httpServer;
+}
+
+
+int VNCServerWin32::run() {
+  { Lock l(runLock);
+    hostThread = Thread::self();
+    runServer = true;
+  }
+
+  // - Register for notification of configuration changes
+  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, IDR_TRAY);
+
+  DWORD result = 0;
+  try {
+    // - Create some managed listening sockets
+    ManagedListener rfb(&sockMgr, &vncServer);
+    ManagedListener http(&sockMgr, httpServer);
+
+    // - Continue to operate until WM_QUIT is processed
+    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);
+
+    vlog.debug("Server exited cleanly");
+  } catch (rdr::SystemException &s) {
+    vlog.error(s.str());
+    result = s.err;
+  } catch (rdr::Exception &e) {
+    vlog.error(e.str());
+  }
+
+  { Lock l(runLock);
+    runServer = false;
+    hostThread = 0;
+  }
+
+  return result;
+}
+
+void VNCServerWin32::stop() {
+  Lock l(runLock);
+  runServer = false;
+  PostThreadMessage(hostThread->getThreadId(), WM_QUIT, 0, 0);
+}
+
+
+bool VNCServerWin32::disconnectClients(const char* reason) {
+  return queueCommand(DisconnectClients, reason, 0);
+}
+
+bool VNCServerWin32::addNewClient(const char* client) {
+  TcpSocket* sock = 0;
+  try {
+    CharArray hostname;
+    int port;
+    getHostAndPort(client, &hostname.buf, &port, 5500);
+    vlog.error("port=%d", port);
+    sock = new TcpSocket(hostname.buf, port);
+    if (queueCommand(AddClient, sock, 0))
+      return true;
+    delete sock;
+  } catch (...) {
+    delete sock;
+  }
+  return false;
+}
+
+
+VNCServerST::queryResult VNCServerWin32::queryConnection(network::Socket* sock,
+                                            const char* userName,
+                                            char** reason)
+{
+  if (queryConnectDialog) {
+    *reason = rfb::strDup("Another connection is currently being queried.");
+    return VNCServerST::REJECT;
+  }
+  queryConnectDialog = new QueryConnectDialog(sock, userName, this);
+  queryConnectDialog->startDialog();
+  return VNCServerST::PENDING;
+}
+
+void VNCServerWin32::queryConnectionComplete() {
+  Thread* qcd = queryConnectDialog;
+  queueCommand(QueryConnectionComplete, 0, 0);
+  delete qcd->join();
+}
+
+
+bool VNCServerWin32::queueCommand(Command cmd, const void* data, int len) {
+  Lock l(commandLock);
+  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;
+  return true;
+}
+
+void VNCServerWin32::doCommand() {
+  Lock l(commandLock);
+  if (command == NoCommand) return;
+
+  // Perform the required command
+  switch (command) {
+
+  case DisconnectClients:
+    // Disconnect all currently active VNC Viewers
+    vncServer.closeClients((const char*)commandData);
+    break;
+
+  case AddClient:
+    // Make a reverse connection to a VNC Viewer
+    vncServer.addClient((network::Socket*)commandData, true);
+    sockMgr.addSocket((network::Socket*)commandData, &vncServer);
+    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;
+
+  default:
+    vlog.error("unknown command %d queued", command);
+  };
+
+  // Clear the command and signal completion
+  command = NoCommand;
+  commandSig.signal();
+}