blob: 2fe599e8f21662445891c362940bac8c9c1d23b4 [file] [log] [blame]
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
Constantin Kaplinsky2c019832008-05-30 11:02:04 +00002 * Copyright (C) 2004-2008 Constantin Kaplinsky. All Rights Reserved.
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +00003 *
4 * This is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This software is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this software; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
17 * USA.
18 */
19
20// FIXME: Check cases when screen width/height is not a multiply of 32.
21// e.g. 800x600.
22
23#include <strings.h>
24#include <sys/types.h>
25#include <sys/stat.h>
26#include <unistd.h>
27#include <errno.h>
28#include <rfb/Logger_stdio.h>
29#include <rfb/LogWriter.h>
30#include <rfb/VNCServerST.h>
31#include <rfb/Configuration.h>
32#include <rfb/SSecurityFactoryStandard.h>
33#include <rfb/Timer.h>
34#include <network/TcpSocket.h>
35#include <tx/TXWindow.h>
36
Constantin Kaplinskya3b60c42006-06-02 04:07:49 +000037#include <vncconfig/QueryConnectDialog.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000038
39#include <signal.h>
40#include <X11/X.h>
41#include <X11/Xlib.h>
42#include <X11/Xutil.h>
43#ifdef HAVE_XTEST
44#include <X11/extensions/XTest.h>
45#endif
46
47#include <x0vncserver/Geometry.h>
48#include <x0vncserver/Image.h>
Constantin Kaplinsky614c7b52007-12-26 18:17:09 +000049#include <x0vncserver/XPixelBuffer.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000050#include <x0vncserver/PollingScheduler.h>
51
52// XXX Lynx/OS 2.3: protos for select(), bzero()
53#ifdef Lynx
54#include <sys/proto.h>
55#endif
56
Constantin Kaplinskyb9632702006-12-01 10:54:55 +000057extern char buildtime[];
58
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000059using namespace rfb;
60using namespace network;
61
62static LogWriter vlog("Main");
63
64IntParameter pollingCycle("PollingCycle", "Milliseconds per one polling "
65 "cycle; actual interval may be dynamically "
66 "adjusted to satisfy MaxProcessorUsage setting", 30);
67IntParameter maxProcessorUsage("MaxProcessorUsage", "Maximum percentage of "
68 "CPU time to be consumed", 35);
69BoolParameter useShm("UseSHM", "Use MIT-SHM extension if available", true);
70BoolParameter useOverlay("OverlayMode", "Use overlay mode under "
71 "IRIX or Solaris", true);
72StringParameter displayname("display", "The X display", "");
73IntParameter rfbport("rfbport", "TCP port to listen for RFB protocol",5900);
74IntParameter queryConnectTimeout("QueryConnectTimeout",
75 "Number of seconds to show the Accept Connection dialog before "
76 "rejecting the connection",
77 10);
78StringParameter hostsFile("HostsFile", "File with IP access control rules", "");
79
80//
81// Allow the main loop terminate itself gracefully on receiving a signal.
82//
83
84static bool caughtSignal = false;
85
86static void CleanupSignalHandler(int sig)
87{
88 caughtSignal = true;
89}
90
91
92class QueryConnHandler : public VNCServerST::QueryConnectionHandler,
93 public QueryResultCallback {
94public:
95 QueryConnHandler(Display* dpy, VNCServerST* vs)
96 : display(dpy), server(vs), queryConnectDialog(0), queryConnectSock(0) {}
97 ~QueryConnHandler() { delete queryConnectDialog; }
98
99 // -=- VNCServerST::QueryConnectionHandler interface
100 virtual VNCServerST::queryResult queryConnection(network::Socket* sock,
101 const char* userName,
102 char** reason) {
103 if (queryConnectSock) {
104 *reason = strDup("Another connection is currently being queried.");
105 return VNCServerST::REJECT;
106 }
107 if (!userName) userName = "(anonymous)";
108 queryConnectSock = sock;
109 CharArray address(sock->getPeerAddress());
110 delete queryConnectDialog;
111 queryConnectDialog = new QueryConnectDialog(display, address.buf,
112 userName, queryConnectTimeout,
113 this);
114 queryConnectDialog->map();
115 return VNCServerST::PENDING;
116 }
117
118 // -=- QueryResultCallback interface
119 virtual void queryApproved() {
120 server->approveConnection(queryConnectSock, true, 0);
121 queryConnectSock = 0;
122 }
123 virtual void queryRejected() {
124 server->approveConnection(queryConnectSock, false,
125 "Connection rejected by local user");
126 queryConnectSock = 0;
127 }
128private:
129 Display* display;
130 VNCServerST* server;
131 QueryConnectDialog* queryConnectDialog;
132 network::Socket* queryConnectSock;
133};
134
135
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000136class XDesktop : public SDesktop, public ColourMap
137{
138public:
139 XDesktop(Display* dpy_, Geometry *geometry_)
Constantin Kaplinsky303433a2008-06-04 05:57:06 +0000140 : dpy(dpy_), geometry(geometry_), pb(0), server(0),
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000141 oldButtonMask(0), haveXtest(false), maxButtons(0), running(false)
142 {
143#ifdef HAVE_XTEST
144 int xtestEventBase;
145 int xtestErrorBase;
146 int major, minor;
147
148 if (XTestQueryExtension(dpy, &xtestEventBase,
149 &xtestErrorBase, &major, &minor)) {
150 XTestGrabControl(dpy, True);
151 vlog.info("XTest extension present - version %d.%d",major,minor);
152 haveXtest = true;
153 } else {
154#endif
155 vlog.info("XTest extension not present");
156 vlog.info("Unable to inject events or display while server is grabbed");
157#ifdef HAVE_XTEST
158 }
159#endif
160
161 }
162 virtual ~XDesktop() {
163 stop();
164 }
165
166 // -=- SDesktop interface
167
168 virtual void start(VNCServer* vs) {
169
170 // Determine actual number of buttons of the X pointer device.
171 unsigned char btnMap[8];
172 int numButtons = XGetPointerMapping(dpy, btnMap, 8);
173 maxButtons = (numButtons > 8) ? 8 : numButtons;
174 vlog.info("Enabling %d button%s of X pointer device",
175 maxButtons, (maxButtons != 1) ? "s" : "");
176
Constantin Kaplinskye0c80c52008-06-04 03:10:05 +0000177 // Create an ImageFactory instance for producing Image objects.
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000178 ImageFactory factory((bool)useShm, (bool)useOverlay);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000179
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000180 // Provide pixel buffer to the server object.
Constantin Kaplinskye0c80c52008-06-04 03:10:05 +0000181 // FIXME: Pass coordinates in a structure?
Constantin Kaplinskyf773a8e2008-06-04 04:30:10 +0000182 pb = new XPixelBuffer(dpy, factory, geometry->getRect(), this);
Constantin Kaplinskye0c80c52008-06-04 03:10:05 +0000183 vlog.info("Allocated %s", pb->getImage()->classDesc());
184
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000185 server = vs;
186 server->setPixelBuffer(pb);
187
188 running = true;
189 }
190
191 virtual void stop() {
192 running = false;
193
194 delete pb;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000195 pb = 0;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000196 }
197
198 inline bool isRunning() {
199 return running;
200 }
201
202 inline void poll() {
Constantin Kaplinsky303433a2008-06-04 05:57:06 +0000203 if (pb)
204 pb->poll(server);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000205 }
206
207 virtual void pointerEvent(const Point& pos, int buttonMask) {
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000208#ifdef HAVE_XTEST
209 if (!haveXtest) return;
210 XTestFakeMotionEvent(dpy, DefaultScreen(dpy),
211 geometry->offsetLeft() + pos.x,
212 geometry->offsetTop() + pos.y,
213 CurrentTime);
214 if (buttonMask != oldButtonMask) {
215 for (int i = 0; i < maxButtons; i++) {
216 if ((buttonMask ^ oldButtonMask) & (1<<i)) {
217 if (buttonMask & (1<<i)) {
218 XTestFakeButtonEvent(dpy, i+1, True, CurrentTime);
219 } else {
220 XTestFakeButtonEvent(dpy, i+1, False, CurrentTime);
221 }
222 }
223 }
224 }
225 oldButtonMask = buttonMask;
226#endif
227 }
228
229 virtual void keyEvent(rdr::U32 key, bool down) {
230#ifdef HAVE_XTEST
231 if (!haveXtest) return;
232 int keycode = XKeysymToKeycode(dpy, key);
233 if (keycode)
234 XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
235#endif
236 }
237
238 virtual void clientCutText(const char* str, int len) {
239 }
240
241 virtual Point getFbSize() {
242 return Point(pb->width(), pb->height());
243 }
244
245 // -=- ColourMap callbacks
246 virtual void lookup(int index, int* r, int* g, int* b) {
247 XColor xc;
248 xc.pixel = index;
249 if (index < DisplayCells(dpy,DefaultScreen(dpy))) {
250 XQueryColor(dpy, DefaultColormap(dpy,DefaultScreen(dpy)), &xc);
251 } else {
252 xc.red = xc.green = xc.blue = 0;
253 }
254 *r = xc.red;
255 *g = xc.green;
256 *b = xc.blue;
257 }
258
259protected:
260 Display* dpy;
261 Geometry* geometry;
Constantin Kaplinsky2c019832008-05-30 11:02:04 +0000262 XPixelBuffer* pb;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000263 VNCServer* server;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000264 int oldButtonMask;
265 bool haveXtest;
266 int maxButtons;
267 bool running;
268};
269
270
271class FileTcpFilter : public TcpFilter
272{
273
274public:
275
276 FileTcpFilter(const char *fname)
277 : TcpFilter("-"), fileName(NULL), lastModTime(0)
278 {
279 if (fname != NULL)
280 fileName = strdup((char *)fname);
281 }
282
283 virtual ~FileTcpFilter()
284 {
285 if (fileName != NULL)
286 free(fileName);
287 }
288
289 virtual bool verifyConnection(Socket* s)
290 {
291 if (!reloadRules()) {
292 vlog.error("Could not read IP filtering rules: rejecting all clients");
293 filter.clear();
294 filter.push_back(parsePattern("-"));
295 return false;
296 }
297
298 return TcpFilter::verifyConnection(s);
299 }
300
301protected:
302
303 bool reloadRules()
304 {
305 if (fileName == NULL)
306 return true;
307
308 struct stat st;
309 if (stat(fileName, &st) != 0)
310 return false;
311
312 if (st.st_mtime != lastModTime) {
313 // Actually reload only if the file was modified
314 FILE *fp = fopen(fileName, "r");
315 if (fp == NULL)
316 return false;
317
318 // Remove all the rules from the parent class
319 filter.clear();
320
321 // Parse the file contents adding rules to the parent class
322 char buf[32];
323 while (readLine(buf, 32, fp)) {
324 if (buf[0] && strchr("+-?", buf[0])) {
325 filter.push_back(parsePattern(buf));
326 }
327 }
328
329 fclose(fp);
330 lastModTime = st.st_mtime;
331 }
332 return true;
333 }
334
335protected:
336
337 char *fileName;
338 time_t lastModTime;
339
340private:
341
342 //
343 // NOTE: we silently truncate long lines in this function.
344 //
345
346 bool readLine(char *buf, int bufSize, FILE *fp)
347 {
348 if (fp == NULL || buf == NULL || bufSize == 0)
349 return false;
350
351 if (fgets(buf, bufSize, fp) == NULL)
352 return false;
353
354 char *ptr = strchr(buf, '\n');
355 if (ptr != NULL) {
356 *ptr = '\0'; // remove newline at the end
357 } else {
358 if (!feof(fp)) {
359 int c;
360 do { // skip the rest of a long line
361 c = getc(fp);
362 } while (c != '\n' && c != EOF);
363 }
364 }
365 return true;
366 }
367
368};
369
370char* programName;
371
372static void usage()
373{
Constantin Kaplinskyb9632702006-12-01 10:54:55 +0000374 fprintf(stderr, "TightVNC Server version %s, built %s\n\n",
375 VERSION, buildtime);
Constantin Kaplinskyf4986f42006-06-02 10:49:03 +0000376 fprintf(stderr, "Usage: %s [<parameters>]\n", programName);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000377 fprintf(stderr,"\n"
378 "Parameters can be turned on with -<param> or off with -<param>=0\n"
379 "Parameters which take a value can be specified as "
380 "-<param> <value>\n"
381 "Other valid forms are <param>=<value> -<param>=<value> "
382 "--<param>=<value>\n"
383 "Parameter names are case-insensitive. The parameters are:\n\n");
384 Configuration::listParams(79, 14);
385 exit(1);
386}
387
388int main(int argc, char** argv)
389{
390 initStdIOLoggers();
391 LogWriter::setLogParams("*:stderr:30");
392
393 programName = argv[0];
394 Display* dpy;
395
396 for (int i = 1; i < argc; i++) {
397 if (Configuration::setParam(argv[i]))
398 continue;
399
400 if (argv[i][0] == '-') {
401 if (i+1 < argc) {
402 if (Configuration::setParam(&argv[i][1], argv[i+1])) {
403 i++;
404 continue;
405 }
406 }
407 usage();
408 }
409
410 usage();
411 }
412
413 CharArray dpyStr(displayname.getData());
414 if (!(dpy = XOpenDisplay(dpyStr.buf[0] ? dpyStr.buf : 0))) {
415 fprintf(stderr,"%s: unable to open display \"%s\"\r\n",
416 programName, XDisplayName(displayname.getData()));
417 exit(1);
418 }
419
420 signal(SIGHUP, CleanupSignalHandler);
421 signal(SIGINT, CleanupSignalHandler);
422 signal(SIGTERM, CleanupSignalHandler);
423
424 try {
425 TXWindow::init(dpy,"x0vncserver");
426 Geometry geo(DisplayWidth(dpy, DefaultScreen(dpy)),
427 DisplayHeight(dpy, DefaultScreen(dpy)));
Constantin Kaplinsky23c60222008-06-04 03:58:07 +0000428 if (geo.getRect().is_empty()) {
429 vlog.error("Exiting with error");
430 return 1;
431 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000432 XDesktop desktop(dpy, &geo);
Constantin Kaplinsky82328312008-04-24 08:44:24 +0000433
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000434 VNCServerST server("x0vncserver", &desktop);
435 QueryConnHandler qcHandler(dpy, &server);
436 server.setQueryConnectionHandler(&qcHandler);
Constantin Kaplinsky82328312008-04-24 08:44:24 +0000437 server.enableVideoSelection(true);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000438
439 TcpListener listener((int)rfbport);
440 vlog.info("Listening on port %d", (int)rfbport);
441
442 FileTcpFilter fileTcpFilter(hostsFile.getData());
443 if (strlen(hostsFile.getData()) != 0)
444 listener.setFilter(&fileTcpFilter);
445
446 PollingScheduler sched((int)pollingCycle, (int)maxProcessorUsage);
447
448 while (!caughtSignal) {
449 struct timeval tv;
450 fd_set rfds;
451 std::list<Socket*> sockets;
452 std::list<Socket*>::iterator i;
453
454 // Process any incoming X events
455 TXWindow::handleXEvents(dpy);
456
457 FD_ZERO(&rfds);
458 FD_SET(listener.getFd(), &rfds);
459 server.getSockets(&sockets);
460 int clients_connected = 0;
461 for (i = sockets.begin(); i != sockets.end(); i++) {
462 if ((*i)->isShutdown()) {
463 server.removeSocket(*i);
464 delete (*i);
465 } else {
466 FD_SET((*i)->getFd(), &rfds);
467 clients_connected++;
468 }
469 }
470
Constantin Kaplinsky813dbb42006-12-05 08:03:18 +0000471 if (!clients_connected)
472 sched.reset();
473
474 if (sched.isRunning()) {
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000475 int wait_ms = sched.millisRemaining();
476 if (wait_ms > 500) {
477 wait_ms = 500;
478 }
479 tv.tv_usec = wait_ms * 1000;
480#ifdef DEBUG
481 // fprintf(stderr, "[%d]\t", wait_ms);
482#endif
483 } else {
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000484 tv.tv_usec = 100000;
485 }
486 tv.tv_sec = 0;
487
488 // Do the wait...
489 sched.sleepStarted();
490 int n = select(FD_SETSIZE, &rfds, 0, 0, &tv);
491 sched.sleepFinished();
492
493 if (n < 0) {
494 if (errno == EINTR) {
495 vlog.debug("Interrupted select() system call");
496 continue;
497 } else {
498 throw rdr::SystemException("select", errno);
499 }
500 }
501
502 // Accept new VNC connections
503 if (FD_ISSET(listener.getFd(), &rfds)) {
504 Socket* sock = listener.accept();
505 if (sock) {
506 server.addSocket(sock);
507 } else {
508 vlog.status("Client connection rejected");
509 }
510 }
511
512 Timer::checkTimeouts();
513 server.checkTimeouts();
514
515 // Client list could have been changed.
516 server.getSockets(&sockets);
517
518 // Nothing more to do if there are no client connections.
519 if (sockets.empty())
520 continue;
521
522 // Process events on existing VNC connections
523 for (i = sockets.begin(); i != sockets.end(); i++) {
524 if (FD_ISSET((*i)->getFd(), &rfds))
525 server.processSocketEvent(*i);
526 }
527
528 if (desktop.isRunning() && sched.goodTimeToPoll()) {
529 sched.newPass();
530 desktop.poll();
531 }
532 }
533
534 } catch (rdr::Exception &e) {
535 vlog.error(e.str());
536 return 1;
537 }
538
539 vlog.info("Terminated");
540 return 0;
541}