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();
+}