blob: 67b3ec51f35b32500d13a8462a7d67e2edf1919f [file] [log] [blame]
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +00001/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved.
2 *
3 * This is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This software is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this software; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
16 * USA.
17 */
18
19// -=- WinVNC Version 4.0 Main Routine
20
21#include <winvnc/VNCServerWin32.h>
22#include <winvnc/resource.h>
23#include <winvnc/STrayIcon.h>
24
25#include <rfb_win32/Win32Util.h>
26#include <rfb_win32/Service.h>
27#include <rfb/SSecurityFactoryStandard.h>
28#include <rfb/Hostname.h>
29#include <rfb/LogWriter.h>
30
31using namespace rfb;
32using namespace win32;
33using namespace winvnc;
34using namespace network;
35
36static LogWriter vlog("VNCServerWin32");
37
38
Peter Åstrand9fb4e0e2004-12-30 10:03:00 +000039const TCHAR* winvnc::VNCServerWin32::RegConfigPath = _T("Software\\TightVNC\\WinVNC4");
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000040
41const UINT VNCM_REG_CHANGED = WM_USER;
42const UINT VNCM_COMMAND = WM_USER + 1;
43
44
45static IntParameter http_port("HTTPPortNumber",
46 "TCP/IP port on which the server will serve the Java applet VNC Viewer ", 5800);
47static IntParameter port_number("PortNumber",
48 "TCP/IP port on which the server will accept connections", 5900);
49static StringParameter hosts("Hosts",
50 "Filter describing which hosts are allowed access to this server", "+");
51static VncAuthPasswdConfigParameter vncAuthPasswd;
52static BoolParameter localHost("LocalHost",
53 "Only accept connections from via the local loop-back network interface", false);
54
55
56// -=- ManagedListener
57// Wrapper class which simplifies the management of a listening socket
58// on a specified port, attached to a SocketManager and SocketServer.
59// Ensures that socket and filter are deleted and updated appropriately.
60
61class ManagedListener {
62public:
63 ManagedListener(win32::SocketManager* mgr, SocketServer* svr)
64 : sock(0), filter(0), port(0), manager(mgr),
65 server(svr), localOnly(0) {}
66 ~ManagedListener() {setPort(0);}
67 void setPort(int port, bool localOnly=false);
68 void setFilter(const char* filter);
69 TcpListener* sock;
70protected:
71 TcpFilter* filter;
72 win32::SocketManager* manager;
73 SocketServer* server;
74 int port;
75 bool localOnly;
76};
77
78// - If the port number/localHost setting has changed then tell the
79// SocketManager to shutdown and delete it. Also remove &
80// delete the filter. Then try to open a socket on the new port.
81void ManagedListener::setPort(int newPort, bool newLocalOnly) {
82 if ((port == newPort) && (localOnly == newLocalOnly) && sock) return;
83 if (sock) {
84 vlog.info("Closed TcpListener on port %d", port);
85 sock->setFilter(0);
86 delete filter;
87 manager->remListener(sock);
88 sock = 0;
89 filter = 0;
90 }
91 port = newPort;
92 localOnly = newLocalOnly;
93 if (port != 0) {
94 try {
95 sock = new TcpListener(port, localOnly);
96 vlog.info("Created TcpListener on port %d%s", port,
97 localOnly ? "(localhost)" : "(any)");
98 } catch (rdr::Exception& e) {
99 vlog.error("TcpListener on port %d failed (%s)", port, e.str());
100 }
101 }
102 if (sock)
103 manager->addListener(sock, server);
104}
105
106void ManagedListener::setFilter(const char* newFilter) {
107 if (!sock) return;
108 vlog.info("Updating TcpListener filter");
109 sock->setFilter(0);
110 delete filter;
111 filter = new TcpFilter(newFilter);
112 sock->setFilter(filter);
113}
114
115
116VNCServerWin32::VNCServerWin32()
117 : vncServer(CStr(ComputerName().buf), &desktop),
118 httpServer(0), runServer(false),
119 isDesktopStarted(false),
120 command(NoCommand), commandSig(commandLock),
121 queryConnectDialog(0) {
122 // Create the Java-viewer HTTP server
123 httpServer = new JavaViewerServer(&vncServer);
124
125 // Initialise the desktop
126 desktop.setStatusLocation(&isDesktopStarted);
127
128 // Initialise the VNC server
129 vncServer.setQueryConnectionHandler(this);
130
131 // Register the desktop's event to be handled
132 sockMgr.addEvent(desktop.getUpdateEvent(), &desktop);
133}
134
135VNCServerWin32::~VNCServerWin32() {
136 // Stop the SDisplay from updating our state
137 desktop.setStatusLocation(0);
138
139 // Destroy the HTTP server
140 delete httpServer;
141}
142
143
144int VNCServerWin32::run() {
145 { Lock l(runLock);
146 hostThread = Thread::self();
147 runServer = true;
148 }
149
150 // - Register for notification of configuration changes
151 if (isServiceProcess())
152 config.setKey(HKEY_LOCAL_MACHINE, RegConfigPath);
153 else
154 config.setKey(HKEY_CURRENT_USER, RegConfigPath);
155 config.setNotifyThread(Thread::self(), VNCM_REG_CHANGED);
156
157 // - Create the tray icon if possible
Oleg Sheikin5c642e92005-12-22 20:57:58 +0000158 STrayIconThread trayIcon(*this, IDI_ICON, IDI_CONNECTED, IDI_ICON_DISABLE,
159 IDI_CONNECTED_DISABLE, IDR_TRAY);
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000160
161 DWORD result = 0;
162 try {
163 // - Create some managed listening sockets
164 ManagedListener rfb(&sockMgr, &vncServer);
165 ManagedListener http(&sockMgr, httpServer);
166
167 // - Continue to operate until WM_QUIT is processed
168 MSG msg;
169 do {
170 // -=- Make sure we're listening on the right ports.
171 rfb.setPort(port_number, localHost);
172 http.setPort(http_port, localHost);
173
174 // -=- Update the Java viewer's web page port number.
175 httpServer->setRFBport(rfb.sock ? port_number : 0);
176
177 // -=- Update the TCP address filter for both ports, if open.
178 CharArray pattern;
179 pattern.buf = hosts.getData();
180 if (!localHost) {
181 rfb.setFilter(pattern.buf);
182 http.setFilter(pattern.buf);
183 }
184
185 // - If there is a listening port then add the address to the
186 // tray icon's tool-tip text.
187 {
188 const TCHAR* prefix = isServiceProcess() ?
189 _T("VNC Server (Service):") : _T("VNC Server (User):");
190
191 std::list<char*> addrs;
192 if (rfb.sock)
193 rfb.sock->getMyAddresses(&addrs);
194 else
195 addrs.push_front(strDup("Not accepting connections"));
196
197 std::list<char*>::iterator i, next_i;
198 int length = _tcslen(prefix)+1;
199 for (i=addrs.begin(); i!= addrs.end(); i++)
200 length += strlen(*i) + 1;
201
202 TCharArray toolTip(length);
203 _tcscpy(toolTip.buf, prefix);
204 for (i=addrs.begin(); i!= addrs.end(); i=next_i) {
205 next_i = i; next_i ++;
206 TCharArray addr = *i; // Assumes ownership of string
207 _tcscat(toolTip.buf, addr.buf);
208 if (next_i != addrs.end())
209 _tcscat(toolTip.buf, _T(","));
210 }
211 trayIcon.setToolTip(toolTip.buf);
212 }
213
214 vlog.debug("Entering message loop");
215
216 // - Run the server until the registry changes, or we're told to quit
217 while (sockMgr.getMessage(&msg, NULL, 0, 0)) {
218 if (msg.hwnd == 0) {
219 if (msg.message == VNCM_REG_CHANGED)
220 break;
221 if (msg.message == VNCM_COMMAND)
222 doCommand();
223 }
224 TranslateMessage(&msg);
225 DispatchMessage(&msg);
226 }
227
228 } while ((msg.message != WM_QUIT) || runServer);
229
230 vlog.debug("Server exited cleanly");
231 } catch (rdr::SystemException &s) {
232 vlog.error(s.str());
233 result = s.err;
234 } catch (rdr::Exception &e) {
235 vlog.error(e.str());
236 }
237
238 { Lock l(runLock);
239 runServer = false;
240 hostThread = 0;
241 }
242
243 return result;
244}
245
246void VNCServerWin32::stop() {
247 Lock l(runLock);
248 runServer = false;
249 PostThreadMessage(hostThread->getThreadId(), WM_QUIT, 0, 0);
250}
251
252
253bool VNCServerWin32::disconnectClients(const char* reason) {
254 return queueCommand(DisconnectClients, reason, 0);
255}
256
257bool VNCServerWin32::addNewClient(const char* client) {
258 TcpSocket* sock = 0;
259 try {
260 CharArray hostname;
261 int port;
262 getHostAndPort(client, &hostname.buf, &port, 5500);
263 vlog.error("port=%d", port);
264 sock = new TcpSocket(hostname.buf, port);
265 if (queueCommand(AddClient, sock, 0))
266 return true;
267 delete sock;
268 } catch (...) {
269 delete sock;
270 }
271 return false;
272}
273
Oleg Sheikin641f7e52005-11-22 18:04:10 +0000274bool VNCServerWin32::getClientsInfo(rfb::ListConnInfo* LCInfo) {
275 return queueCommand(GetClientsInfo, LCInfo, 0);
276}
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000277
Oleg Sheikin4b0304f2005-12-09 10:59:12 +0000278bool VNCServerWin32::setClientsStatus(rfb::ListConnInfo* LCInfo) {
279 return queueCommand(SetClientsStatus, LCInfo, 0);
280}
281
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000282VNCServerST::queryResult VNCServerWin32::queryConnection(network::Socket* sock,
283 const char* userName,
284 char** reason)
285{
286 if (queryConnectDialog) {
287 *reason = rfb::strDup("Another connection is currently being queried.");
288 return VNCServerST::REJECT;
289 }
290 queryConnectDialog = new QueryConnectDialog(sock, userName, this);
291 queryConnectDialog->startDialog();
292 return VNCServerST::PENDING;
293}
294
295void VNCServerWin32::queryConnectionComplete() {
296 Thread* qcd = queryConnectDialog;
297 queueCommand(QueryConnectionComplete, 0, 0);
298 delete qcd->join();
299}
300
301
302bool VNCServerWin32::queueCommand(Command cmd, const void* data, int len) {
303 Lock l(commandLock);
304 while (command != NoCommand) commandSig.wait();
305 command = cmd;
306 commandData = data;
307 commandDataLen = len;
308 if (PostThreadMessage(hostThread->getThreadId(), VNCM_COMMAND, 0, 0))
309 while (command != NoCommand) commandSig.wait();
310 else
311 return false;
312 return true;
313}
314
315void VNCServerWin32::doCommand() {
316 Lock l(commandLock);
317 if (command == NoCommand) return;
318
319 // Perform the required command
320 switch (command) {
321
322 case DisconnectClients:
323 // Disconnect all currently active VNC Viewers
324 vncServer.closeClients((const char*)commandData);
325 break;
326
327 case AddClient:
328 // Make a reverse connection to a VNC Viewer
329 vncServer.addClient((network::Socket*)commandData, true);
330 sockMgr.addSocket((network::Socket*)commandData, &vncServer);
331 break;
332
333 case QueryConnectionComplete:
334 // The Accept/Reject dialog has completed
335 // Get the result, then clean it up
336 vncServer.approveConnection(queryConnectDialog->getSock(),
337 queryConnectDialog->isAccepted(),
338 "Connection rejected by user");
339 queryConnectDialog = 0;
340 break;
Oleg Sheikin641f7e52005-11-22 18:04:10 +0000341 case GetClientsInfo:
342 vncServer.getConnInfo((ListConnInfo*)commandData);
343 break;
Oleg Sheikin4b0304f2005-12-09 10:59:12 +0000344 case SetClientsStatus:
345 vncServer.setConnStatus((ListConnInfo*)commandData);
346 break;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000347
348 default:
349 vlog.error("unknown command %d queued", command);
350 };
351
352 // Clear the command and signal completion
353 command = NoCommand;
354 commandSig.signal();
355}