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