blob: 2d3876185a961736723dc26f88f0c49dedfb0aec [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/PollingManager.h>
51#include <x0vncserver/PollingScheduler.h>
52
53// XXX Lynx/OS 2.3: protos for select(), bzero()
54#ifdef Lynx
55#include <sys/proto.h>
56#endif
57
Constantin Kaplinskyb9632702006-12-01 10:54:55 +000058extern char buildtime[];
59
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000060using namespace rfb;
61using namespace network;
62
63static LogWriter vlog("Main");
64
65IntParameter pollingCycle("PollingCycle", "Milliseconds per one polling "
66 "cycle; actual interval may be dynamically "
67 "adjusted to satisfy MaxProcessorUsage setting", 30);
68IntParameter maxProcessorUsage("MaxProcessorUsage", "Maximum percentage of "
69 "CPU time to be consumed", 35);
70BoolParameter useShm("UseSHM", "Use MIT-SHM extension if available", true);
71BoolParameter useOverlay("OverlayMode", "Use overlay mode under "
72 "IRIX or Solaris", true);
73StringParameter displayname("display", "The X display", "");
74IntParameter rfbport("rfbport", "TCP port to listen for RFB protocol",5900);
75IntParameter queryConnectTimeout("QueryConnectTimeout",
76 "Number of seconds to show the Accept Connection dialog before "
77 "rejecting the connection",
78 10);
79StringParameter hostsFile("HostsFile", "File with IP access control rules", "");
80
81//
82// Allow the main loop terminate itself gracefully on receiving a signal.
83//
84
85static bool caughtSignal = false;
86
87static void CleanupSignalHandler(int sig)
88{
89 caughtSignal = true;
90}
91
92
93class QueryConnHandler : public VNCServerST::QueryConnectionHandler,
94 public QueryResultCallback {
95public:
96 QueryConnHandler(Display* dpy, VNCServerST* vs)
97 : display(dpy), server(vs), queryConnectDialog(0), queryConnectSock(0) {}
98 ~QueryConnHandler() { delete queryConnectDialog; }
99
100 // -=- VNCServerST::QueryConnectionHandler interface
101 virtual VNCServerST::queryResult queryConnection(network::Socket* sock,
102 const char* userName,
103 char** reason) {
104 if (queryConnectSock) {
105 *reason = strDup("Another connection is currently being queried.");
106 return VNCServerST::REJECT;
107 }
108 if (!userName) userName = "(anonymous)";
109 queryConnectSock = sock;
110 CharArray address(sock->getPeerAddress());
111 delete queryConnectDialog;
112 queryConnectDialog = new QueryConnectDialog(display, address.buf,
113 userName, queryConnectTimeout,
114 this);
115 queryConnectDialog->map();
116 return VNCServerST::PENDING;
117 }
118
119 // -=- QueryResultCallback interface
120 virtual void queryApproved() {
121 server->approveConnection(queryConnectSock, true, 0);
122 queryConnectSock = 0;
123 }
124 virtual void queryRejected() {
125 server->approveConnection(queryConnectSock, false,
126 "Connection rejected by local user");
127 queryConnectSock = 0;
128 }
129private:
130 Display* display;
131 VNCServerST* server;
132 QueryConnectDialog* queryConnectDialog;
133 network::Socket* queryConnectSock;
134};
135
136
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000137class XDesktop : public SDesktop, public ColourMap
138{
139public:
140 XDesktop(Display* dpy_, Geometry *geometry_)
Constantin Kaplinskye0c80c52008-06-04 03:10:05 +0000141 : dpy(dpy_), geometry(geometry_), pb(0), server(0), pollmgr(0),
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000142 oldButtonMask(0), haveXtest(false), maxButtons(0), running(false)
143 {
144#ifdef HAVE_XTEST
145 int xtestEventBase;
146 int xtestErrorBase;
147 int major, minor;
148
149 if (XTestQueryExtension(dpy, &xtestEventBase,
150 &xtestErrorBase, &major, &minor)) {
151 XTestGrabControl(dpy, True);
152 vlog.info("XTest extension present - version %d.%d",major,minor);
153 haveXtest = true;
154 } else {
155#endif
156 vlog.info("XTest extension not present");
157 vlog.info("Unable to inject events or display while server is grabbed");
158#ifdef HAVE_XTEST
159 }
160#endif
161
162 }
163 virtual ~XDesktop() {
164 stop();
165 }
166
167 // -=- SDesktop interface
168
169 virtual void start(VNCServer* vs) {
170
171 // Determine actual number of buttons of the X pointer device.
172 unsigned char btnMap[8];
173 int numButtons = XGetPointerMapping(dpy, btnMap, 8);
174 maxButtons = (numButtons > 8) ? 8 : numButtons;
175 vlog.info("Enabling %d button%s of X pointer device",
176 maxButtons, (maxButtons != 1) ? "s" : "");
177
Constantin Kaplinskye0c80c52008-06-04 03:10:05 +0000178 // Create an ImageFactory instance for producing Image objects.
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000179 ImageFactory factory((bool)useShm, (bool)useOverlay);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000180
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000181 // Provide pixel buffer to the server object.
Constantin Kaplinskye0c80c52008-06-04 03:10:05 +0000182 // FIXME: Pass coordinates in a structure?
Constantin Kaplinskyf773a8e2008-06-04 04:30:10 +0000183 pb = new XPixelBuffer(dpy, factory, geometry->getRect(), this);
Constantin Kaplinskye0c80c52008-06-04 03:10:05 +0000184 vlog.info("Allocated %s", pb->getImage()->classDesc());
185
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000186 server = vs;
187 server->setPixelBuffer(pb);
188
Constantin Kaplinsky2c019832008-05-30 11:02:04 +0000189 // Create polling manager object for detection of pixel changes.
190 pollmgr = new PollingManager(dpy, pb, &factory,
191 geometry->offsetLeft(),
192 geometry->offsetTop());
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000193 running = true;
194 }
195
196 virtual void stop() {
197 running = false;
198
199 delete pb;
200 delete pollmgr;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000201
202 pb = 0;
203 pollmgr = 0;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000204 }
205
206 inline bool isRunning() {
207 return running;
208 }
209
210 inline void poll() {
211 if (pollmgr)
Constantin Kaplinsky54cfef32008-06-03 07:04:17 +0000212 pollmgr->poll(server);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000213 }
214
215 virtual void pointerEvent(const Point& pos, int buttonMask) {
216 pollmgr->setPointerPos(pos);
217#ifdef HAVE_XTEST
218 if (!haveXtest) return;
219 XTestFakeMotionEvent(dpy, DefaultScreen(dpy),
220 geometry->offsetLeft() + pos.x,
221 geometry->offsetTop() + pos.y,
222 CurrentTime);
223 if (buttonMask != oldButtonMask) {
224 for (int i = 0; i < maxButtons; i++) {
225 if ((buttonMask ^ oldButtonMask) & (1<<i)) {
226 if (buttonMask & (1<<i)) {
227 XTestFakeButtonEvent(dpy, i+1, True, CurrentTime);
228 } else {
229 XTestFakeButtonEvent(dpy, i+1, False, CurrentTime);
230 }
231 }
232 }
233 }
234 oldButtonMask = buttonMask;
235#endif
236 }
237
238 virtual void keyEvent(rdr::U32 key, bool down) {
239#ifdef HAVE_XTEST
240 if (!haveXtest) return;
241 int keycode = XKeysymToKeycode(dpy, key);
242 if (keycode)
243 XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
244#endif
245 }
246
247 virtual void clientCutText(const char* str, int len) {
248 }
249
250 virtual Point getFbSize() {
251 return Point(pb->width(), pb->height());
252 }
253
254 // -=- ColourMap callbacks
255 virtual void lookup(int index, int* r, int* g, int* b) {
256 XColor xc;
257 xc.pixel = index;
258 if (index < DisplayCells(dpy,DefaultScreen(dpy))) {
259 XQueryColor(dpy, DefaultColormap(dpy,DefaultScreen(dpy)), &xc);
260 } else {
261 xc.red = xc.green = xc.blue = 0;
262 }
263 *r = xc.red;
264 *g = xc.green;
265 *b = xc.blue;
266 }
267
268protected:
269 Display* dpy;
270 Geometry* geometry;
Constantin Kaplinsky2c019832008-05-30 11:02:04 +0000271 XPixelBuffer* pb;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000272 VNCServer* server;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000273 PollingManager* pollmgr;
274 int oldButtonMask;
275 bool haveXtest;
276 int maxButtons;
277 bool running;
278};
279
280
281class FileTcpFilter : public TcpFilter
282{
283
284public:
285
286 FileTcpFilter(const char *fname)
287 : TcpFilter("-"), fileName(NULL), lastModTime(0)
288 {
289 if (fname != NULL)
290 fileName = strdup((char *)fname);
291 }
292
293 virtual ~FileTcpFilter()
294 {
295 if (fileName != NULL)
296 free(fileName);
297 }
298
299 virtual bool verifyConnection(Socket* s)
300 {
301 if (!reloadRules()) {
302 vlog.error("Could not read IP filtering rules: rejecting all clients");
303 filter.clear();
304 filter.push_back(parsePattern("-"));
305 return false;
306 }
307
308 return TcpFilter::verifyConnection(s);
309 }
310
311protected:
312
313 bool reloadRules()
314 {
315 if (fileName == NULL)
316 return true;
317
318 struct stat st;
319 if (stat(fileName, &st) != 0)
320 return false;
321
322 if (st.st_mtime != lastModTime) {
323 // Actually reload only if the file was modified
324 FILE *fp = fopen(fileName, "r");
325 if (fp == NULL)
326 return false;
327
328 // Remove all the rules from the parent class
329 filter.clear();
330
331 // Parse the file contents adding rules to the parent class
332 char buf[32];
333 while (readLine(buf, 32, fp)) {
334 if (buf[0] && strchr("+-?", buf[0])) {
335 filter.push_back(parsePattern(buf));
336 }
337 }
338
339 fclose(fp);
340 lastModTime = st.st_mtime;
341 }
342 return true;
343 }
344
345protected:
346
347 char *fileName;
348 time_t lastModTime;
349
350private:
351
352 //
353 // NOTE: we silently truncate long lines in this function.
354 //
355
356 bool readLine(char *buf, int bufSize, FILE *fp)
357 {
358 if (fp == NULL || buf == NULL || bufSize == 0)
359 return false;
360
361 if (fgets(buf, bufSize, fp) == NULL)
362 return false;
363
364 char *ptr = strchr(buf, '\n');
365 if (ptr != NULL) {
366 *ptr = '\0'; // remove newline at the end
367 } else {
368 if (!feof(fp)) {
369 int c;
370 do { // skip the rest of a long line
371 c = getc(fp);
372 } while (c != '\n' && c != EOF);
373 }
374 }
375 return true;
376 }
377
378};
379
380char* programName;
381
382static void usage()
383{
Constantin Kaplinskyb9632702006-12-01 10:54:55 +0000384 fprintf(stderr, "TightVNC Server version %s, built %s\n\n",
385 VERSION, buildtime);
Constantin Kaplinskyf4986f42006-06-02 10:49:03 +0000386 fprintf(stderr, "Usage: %s [<parameters>]\n", programName);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000387 fprintf(stderr,"\n"
388 "Parameters can be turned on with -<param> or off with -<param>=0\n"
389 "Parameters which take a value can be specified as "
390 "-<param> <value>\n"
391 "Other valid forms are <param>=<value> -<param>=<value> "
392 "--<param>=<value>\n"
393 "Parameter names are case-insensitive. The parameters are:\n\n");
394 Configuration::listParams(79, 14);
395 exit(1);
396}
397
398int main(int argc, char** argv)
399{
400 initStdIOLoggers();
401 LogWriter::setLogParams("*:stderr:30");
402
403 programName = argv[0];
404 Display* dpy;
405
406 for (int i = 1; i < argc; i++) {
407 if (Configuration::setParam(argv[i]))
408 continue;
409
410 if (argv[i][0] == '-') {
411 if (i+1 < argc) {
412 if (Configuration::setParam(&argv[i][1], argv[i+1])) {
413 i++;
414 continue;
415 }
416 }
417 usage();
418 }
419
420 usage();
421 }
422
423 CharArray dpyStr(displayname.getData());
424 if (!(dpy = XOpenDisplay(dpyStr.buf[0] ? dpyStr.buf : 0))) {
425 fprintf(stderr,"%s: unable to open display \"%s\"\r\n",
426 programName, XDisplayName(displayname.getData()));
427 exit(1);
428 }
429
430 signal(SIGHUP, CleanupSignalHandler);
431 signal(SIGINT, CleanupSignalHandler);
432 signal(SIGTERM, CleanupSignalHandler);
433
434 try {
435 TXWindow::init(dpy,"x0vncserver");
436 Geometry geo(DisplayWidth(dpy, DefaultScreen(dpy)),
437 DisplayHeight(dpy, DefaultScreen(dpy)));
Constantin Kaplinsky23c60222008-06-04 03:58:07 +0000438 if (geo.getRect().is_empty()) {
439 vlog.error("Exiting with error");
440 return 1;
441 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000442 XDesktop desktop(dpy, &geo);
Constantin Kaplinsky82328312008-04-24 08:44:24 +0000443
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000444 VNCServerST server("x0vncserver", &desktop);
445 QueryConnHandler qcHandler(dpy, &server);
446 server.setQueryConnectionHandler(&qcHandler);
Constantin Kaplinsky82328312008-04-24 08:44:24 +0000447 server.enableVideoSelection(true);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000448
449 TcpListener listener((int)rfbport);
450 vlog.info("Listening on port %d", (int)rfbport);
451
452 FileTcpFilter fileTcpFilter(hostsFile.getData());
453 if (strlen(hostsFile.getData()) != 0)
454 listener.setFilter(&fileTcpFilter);
455
456 PollingScheduler sched((int)pollingCycle, (int)maxProcessorUsage);
457
458 while (!caughtSignal) {
459 struct timeval tv;
460 fd_set rfds;
461 std::list<Socket*> sockets;
462 std::list<Socket*>::iterator i;
463
464 // Process any incoming X events
465 TXWindow::handleXEvents(dpy);
466
467 FD_ZERO(&rfds);
468 FD_SET(listener.getFd(), &rfds);
469 server.getSockets(&sockets);
470 int clients_connected = 0;
471 for (i = sockets.begin(); i != sockets.end(); i++) {
472 if ((*i)->isShutdown()) {
473 server.removeSocket(*i);
474 delete (*i);
475 } else {
476 FD_SET((*i)->getFd(), &rfds);
477 clients_connected++;
478 }
479 }
480
Constantin Kaplinsky813dbb42006-12-05 08:03:18 +0000481 if (!clients_connected)
482 sched.reset();
483
484 if (sched.isRunning()) {
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000485 int wait_ms = sched.millisRemaining();
486 if (wait_ms > 500) {
487 wait_ms = 500;
488 }
489 tv.tv_usec = wait_ms * 1000;
490#ifdef DEBUG
491 // fprintf(stderr, "[%d]\t", wait_ms);
492#endif
493 } else {
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000494 tv.tv_usec = 100000;
495 }
496 tv.tv_sec = 0;
497
498 // Do the wait...
499 sched.sleepStarted();
500 int n = select(FD_SETSIZE, &rfds, 0, 0, &tv);
501 sched.sleepFinished();
502
503 if (n < 0) {
504 if (errno == EINTR) {
505 vlog.debug("Interrupted select() system call");
506 continue;
507 } else {
508 throw rdr::SystemException("select", errno);
509 }
510 }
511
512 // Accept new VNC connections
513 if (FD_ISSET(listener.getFd(), &rfds)) {
514 Socket* sock = listener.accept();
515 if (sock) {
516 server.addSocket(sock);
517 } else {
518 vlog.status("Client connection rejected");
519 }
520 }
521
522 Timer::checkTimeouts();
523 server.checkTimeouts();
524
525 // Client list could have been changed.
526 server.getSockets(&sockets);
527
528 // Nothing more to do if there are no client connections.
529 if (sockets.empty())
530 continue;
531
532 // Process events on existing VNC connections
533 for (i = sockets.begin(); i != sockets.end(); i++) {
534 if (FD_ISSET((*i)->getFd(), &rfds))
535 server.processSocketEvent(*i);
536 }
537
538 if (desktop.isRunning() && sched.goodTimeToPoll()) {
539 sched.newPass();
540 desktop.poll();
541 }
542 }
543
544 } catch (rdr::Exception &e) {
545 vlog.error(e.str());
546 return 1;
547 }
548
549 vlog.info("Terminated");
550 return 0;
551}