blob: a870cb1d6be97a3beb808787434692382d17f9a6 [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
39const TCHAR* winvnc::VNCServerWin32::RegConfigPath = _T("Software\\RealVNC\\WinVNC4");
40
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
158 STrayIconThread trayIcon(*this, IDI_ICON, IDI_CONNECTED, IDR_TRAY);
159
160 DWORD result = 0;
161 try {
162 // - Create some managed listening sockets
163 ManagedListener rfb(&sockMgr, &vncServer);
164 ManagedListener http(&sockMgr, httpServer);
165
166 // - Continue to operate until WM_QUIT is processed
167 MSG msg;
168 do {
169 // -=- Make sure we're listening on the right ports.
170 rfb.setPort(port_number, localHost);
171 http.setPort(http_port, localHost);
172
173 // -=- Update the Java viewer's web page port number.
174 httpServer->setRFBport(rfb.sock ? port_number : 0);
175
176 // -=- Update the TCP address filter for both ports, if open.
177 CharArray pattern;
178 pattern.buf = hosts.getData();
179 if (!localHost) {
180 rfb.setFilter(pattern.buf);
181 http.setFilter(pattern.buf);
182 }
183
184 // - If there is a listening port then add the address to the
185 // tray icon's tool-tip text.
186 {
187 const TCHAR* prefix = isServiceProcess() ?
188 _T("VNC Server (Service):") : _T("VNC Server (User):");
189
190 std::list<char*> addrs;
191 if (rfb.sock)
192 rfb.sock->getMyAddresses(&addrs);
193 else
194 addrs.push_front(strDup("Not accepting connections"));
195
196 std::list<char*>::iterator i, next_i;
197 int length = _tcslen(prefix)+1;
198 for (i=addrs.begin(); i!= addrs.end(); i++)
199 length += strlen(*i) + 1;
200
201 TCharArray toolTip(length);
202 _tcscpy(toolTip.buf, prefix);
203 for (i=addrs.begin(); i!= addrs.end(); i=next_i) {
204 next_i = i; next_i ++;
205 TCharArray addr = *i; // Assumes ownership of string
206 _tcscat(toolTip.buf, addr.buf);
207 if (next_i != addrs.end())
208 _tcscat(toolTip.buf, _T(","));
209 }
210 trayIcon.setToolTip(toolTip.buf);
211 }
212
213 vlog.debug("Entering message loop");
214
215 // - Run the server until the registry changes, or we're told to quit
216 while (sockMgr.getMessage(&msg, NULL, 0, 0)) {
217 if (msg.hwnd == 0) {
218 if (msg.message == VNCM_REG_CHANGED)
219 break;
220 if (msg.message == VNCM_COMMAND)
221 doCommand();
222 }
223 TranslateMessage(&msg);
224 DispatchMessage(&msg);
225 }
226
227 } while ((msg.message != WM_QUIT) || runServer);
228
229 vlog.debug("Server exited cleanly");
230 } catch (rdr::SystemException &s) {
231 vlog.error(s.str());
232 result = s.err;
233 } catch (rdr::Exception &e) {
234 vlog.error(e.str());
235 }
236
237 { Lock l(runLock);
238 runServer = false;
239 hostThread = 0;
240 }
241
242 return result;
243}
244
245void VNCServerWin32::stop() {
246 Lock l(runLock);
247 runServer = false;
248 PostThreadMessage(hostThread->getThreadId(), WM_QUIT, 0, 0);
249}
250
251
252bool VNCServerWin32::disconnectClients(const char* reason) {
253 return queueCommand(DisconnectClients, reason, 0);
254}
255
256bool VNCServerWin32::addNewClient(const char* client) {
257 TcpSocket* sock = 0;
258 try {
259 CharArray hostname;
260 int port;
261 getHostAndPort(client, &hostname.buf, &port, 5500);
262 vlog.error("port=%d", port);
263 sock = new TcpSocket(hostname.buf, port);
264 if (queueCommand(AddClient, sock, 0))
265 return true;
266 delete sock;
267 } catch (...) {
268 delete sock;
269 }
270 return false;
271}
272
273
274VNCServerST::queryResult VNCServerWin32::queryConnection(network::Socket* sock,
275 const char* userName,
276 char** reason)
277{
278 if (queryConnectDialog) {
279 *reason = rfb::strDup("Another connection is currently being queried.");
280 return VNCServerST::REJECT;
281 }
282 queryConnectDialog = new QueryConnectDialog(sock, userName, this);
283 queryConnectDialog->startDialog();
284 return VNCServerST::PENDING;
285}
286
287void VNCServerWin32::queryConnectionComplete() {
288 Thread* qcd = queryConnectDialog;
289 queueCommand(QueryConnectionComplete, 0, 0);
290 delete qcd->join();
291}
292
293
294bool VNCServerWin32::queueCommand(Command cmd, const void* data, int len) {
295 Lock l(commandLock);
296 while (command != NoCommand) commandSig.wait();
297 command = cmd;
298 commandData = data;
299 commandDataLen = len;
300 if (PostThreadMessage(hostThread->getThreadId(), VNCM_COMMAND, 0, 0))
301 while (command != NoCommand) commandSig.wait();
302 else
303 return false;
304 return true;
305}
306
307void VNCServerWin32::doCommand() {
308 Lock l(commandLock);
309 if (command == NoCommand) return;
310
311 // Perform the required command
312 switch (command) {
313
314 case DisconnectClients:
315 // Disconnect all currently active VNC Viewers
316 vncServer.closeClients((const char*)commandData);
317 break;
318
319 case AddClient:
320 // Make a reverse connection to a VNC Viewer
321 vncServer.addClient((network::Socket*)commandData, true);
322 sockMgr.addSocket((network::Socket*)commandData, &vncServer);
323 break;
324
325 case QueryConnectionComplete:
326 // The Accept/Reject dialog has completed
327 // Get the result, then clean it up
328 vncServer.approveConnection(queryConnectDialog->getSock(),
329 queryConnectDialog->isAccepted(),
330 "Connection rejected by user");
331 queryConnectDialog = 0;
332 break;
333
334 default:
335 vlog.error("unknown command %d queued", command);
336 };
337
338 // Clear the command and signal completion
339 command = NoCommand;
340 commandSig.signal();
341}