blob: 48be662ed8f1909cd7c915226645a984ab2c25cb [file] [log] [blame]
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
2 * Copyright (C) 2004-2006 Constantin Kaplinsky. All Rights Reserved.
3 *
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>
49#include <x0vncserver/PollingManager.h>
50#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
136//
137// XPixelBuffer is a modification of FullFramePixelBuffer that does
138// not always return buffer width in getStride().
139//
140
141class XPixelBuffer : public FullFramePixelBuffer
142{
143public:
144 XPixelBuffer(const PixelFormat& pf, int width, int height,
145 rdr::U8* data_, ColourMap* cm, int stride_) :
146 FullFramePixelBuffer(pf, width, height, data_, cm), stride(stride_)
147 {
148 }
149
150 virtual int getStride() const { return stride; }
151
152protected:
153 int stride;
154};
155
156
157class XDesktop : public SDesktop, public ColourMap
158{
159public:
160 XDesktop(Display* dpy_, Geometry *geometry_)
161 : dpy(dpy_), geometry(geometry_), pb(0), server(0), image(0), pollmgr(0),
162 oldButtonMask(0), haveXtest(false), maxButtons(0), running(false)
163 {
164#ifdef HAVE_XTEST
165 int xtestEventBase;
166 int xtestErrorBase;
167 int major, minor;
168
169 if (XTestQueryExtension(dpy, &xtestEventBase,
170 &xtestErrorBase, &major, &minor)) {
171 XTestGrabControl(dpy, True);
172 vlog.info("XTest extension present - version %d.%d",major,minor);
173 haveXtest = true;
174 } else {
175#endif
176 vlog.info("XTest extension not present");
177 vlog.info("Unable to inject events or display while server is grabbed");
178#ifdef HAVE_XTEST
179 }
180#endif
181
182 }
183 virtual ~XDesktop() {
184 stop();
185 }
186
187 // -=- SDesktop interface
188
189 virtual void start(VNCServer* vs) {
190
191 // Determine actual number of buttons of the X pointer device.
192 unsigned char btnMap[8];
193 int numButtons = XGetPointerMapping(dpy, btnMap, 8);
194 maxButtons = (numButtons > 8) ? 8 : numButtons;
195 vlog.info("Enabling %d button%s of X pointer device",
196 maxButtons, (maxButtons != 1) ? "s" : "");
197
198 // Create an image for maintaining framebuffer data.
199 ImageFactory factory((bool)useShm, (bool)useOverlay);
200 image = factory.newImage(dpy, geometry->width(), geometry->height());
201 vlog.info("Allocated %s", image->classDesc());
202
203 // Create polling manager object. It will track screen changes and
204 // keep pixels of the `image' object up to date.
205 pollmgr = new PollingManager(dpy, image, &factory,
206 geometry->offsetLeft(),
207 geometry->offsetTop());
208 pollmgr->setVNCServer(vs);
209
210 pf.bpp = image->xim->bits_per_pixel;
211 pf.depth = image->xim->depth;
212 pf.bigEndian = (image->xim->byte_order == MSBFirst);
213 pf.trueColour = image->isTrueColor();
214 pf.redShift = ffs(image->xim->red_mask) - 1;
215 pf.greenShift = ffs(image->xim->green_mask) - 1;
216 pf.blueShift = ffs(image->xim->blue_mask) - 1;
217 pf.redMax = image->xim->red_mask >> pf.redShift;
218 pf.greenMax = image->xim->green_mask >> pf.greenShift;
219 pf.blueMax = image->xim->blue_mask >> pf.blueShift;
220
221 // Calculate the number of pixels in a row, with padding included.
222 int stride = image->xim->bytes_per_line * 8 / image->xim->bits_per_pixel;
223
224 // Provide pixel buffer to the server object.
225 pb = new XPixelBuffer(pf, geometry->width(), geometry->height(),
226 (rdr::U8*)image->xim->data, this, stride);
227 server = vs;
228 server->setPixelBuffer(pb);
229
230 running = true;
231 }
232
233 virtual void stop() {
234 running = false;
235
236 delete pb;
237 delete pollmgr;
238 delete image;
239
240 pb = 0;
241 pollmgr = 0;
242 image = 0;
243 }
244
245 inline bool isRunning() {
246 return running;
247 }
248
249 inline void poll() {
250 if (pollmgr)
251 pollmgr->poll();
252 }
253
254 virtual void pointerEvent(const Point& pos, int buttonMask) {
255 pollmgr->setPointerPos(pos);
256#ifdef HAVE_XTEST
257 if (!haveXtest) return;
258 XTestFakeMotionEvent(dpy, DefaultScreen(dpy),
259 geometry->offsetLeft() + pos.x,
260 geometry->offsetTop() + pos.y,
261 CurrentTime);
262 if (buttonMask != oldButtonMask) {
263 for (int i = 0; i < maxButtons; i++) {
264 if ((buttonMask ^ oldButtonMask) & (1<<i)) {
265 if (buttonMask & (1<<i)) {
266 XTestFakeButtonEvent(dpy, i+1, True, CurrentTime);
267 } else {
268 XTestFakeButtonEvent(dpy, i+1, False, CurrentTime);
269 }
270 }
271 }
272 }
273 oldButtonMask = buttonMask;
274#endif
275 }
276
277 virtual void keyEvent(rdr::U32 key, bool down) {
278#ifdef HAVE_XTEST
279 if (!haveXtest) return;
280 int keycode = XKeysymToKeycode(dpy, key);
281 if (keycode)
282 XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
283#endif
284 }
285
286 virtual void clientCutText(const char* str, int len) {
287 }
288
289 virtual Point getFbSize() {
290 return Point(pb->width(), pb->height());
291 }
292
293 // -=- ColourMap callbacks
294 virtual void lookup(int index, int* r, int* g, int* b) {
295 XColor xc;
296 xc.pixel = index;
297 if (index < DisplayCells(dpy,DefaultScreen(dpy))) {
298 XQueryColor(dpy, DefaultColormap(dpy,DefaultScreen(dpy)), &xc);
299 } else {
300 xc.red = xc.green = xc.blue = 0;
301 }
302 *r = xc.red;
303 *g = xc.green;
304 *b = xc.blue;
305 }
306
307protected:
308 Display* dpy;
309 Geometry* geometry;
310 PixelFormat pf;
311 PixelBuffer* pb;
312 VNCServer* server;
313 Image* image;
314 PollingManager* pollmgr;
315 int oldButtonMask;
316 bool haveXtest;
317 int maxButtons;
318 bool running;
319};
320
321
322class FileTcpFilter : public TcpFilter
323{
324
325public:
326
327 FileTcpFilter(const char *fname)
328 : TcpFilter("-"), fileName(NULL), lastModTime(0)
329 {
330 if (fname != NULL)
331 fileName = strdup((char *)fname);
332 }
333
334 virtual ~FileTcpFilter()
335 {
336 if (fileName != NULL)
337 free(fileName);
338 }
339
340 virtual bool verifyConnection(Socket* s)
341 {
342 if (!reloadRules()) {
343 vlog.error("Could not read IP filtering rules: rejecting all clients");
344 filter.clear();
345 filter.push_back(parsePattern("-"));
346 return false;
347 }
348
349 return TcpFilter::verifyConnection(s);
350 }
351
352protected:
353
354 bool reloadRules()
355 {
356 if (fileName == NULL)
357 return true;
358
359 struct stat st;
360 if (stat(fileName, &st) != 0)
361 return false;
362
363 if (st.st_mtime != lastModTime) {
364 // Actually reload only if the file was modified
365 FILE *fp = fopen(fileName, "r");
366 if (fp == NULL)
367 return false;
368
369 // Remove all the rules from the parent class
370 filter.clear();
371
372 // Parse the file contents adding rules to the parent class
373 char buf[32];
374 while (readLine(buf, 32, fp)) {
375 if (buf[0] && strchr("+-?", buf[0])) {
376 filter.push_back(parsePattern(buf));
377 }
378 }
379
380 fclose(fp);
381 lastModTime = st.st_mtime;
382 }
383 return true;
384 }
385
386protected:
387
388 char *fileName;
389 time_t lastModTime;
390
391private:
392
393 //
394 // NOTE: we silently truncate long lines in this function.
395 //
396
397 bool readLine(char *buf, int bufSize, FILE *fp)
398 {
399 if (fp == NULL || buf == NULL || bufSize == 0)
400 return false;
401
402 if (fgets(buf, bufSize, fp) == NULL)
403 return false;
404
405 char *ptr = strchr(buf, '\n');
406 if (ptr != NULL) {
407 *ptr = '\0'; // remove newline at the end
408 } else {
409 if (!feof(fp)) {
410 int c;
411 do { // skip the rest of a long line
412 c = getc(fp);
413 } while (c != '\n' && c != EOF);
414 }
415 }
416 return true;
417 }
418
419};
420
421char* programName;
422
423static void usage()
424{
Constantin Kaplinskyb9632702006-12-01 10:54:55 +0000425 fprintf(stderr, "TightVNC Server version %s, built %s\n\n",
426 VERSION, buildtime);
Constantin Kaplinskyf4986f42006-06-02 10:49:03 +0000427 fprintf(stderr, "Usage: %s [<parameters>]\n", programName);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000428 fprintf(stderr,"\n"
429 "Parameters can be turned on with -<param> or off with -<param>=0\n"
430 "Parameters which take a value can be specified as "
431 "-<param> <value>\n"
432 "Other valid forms are <param>=<value> -<param>=<value> "
433 "--<param>=<value>\n"
434 "Parameter names are case-insensitive. The parameters are:\n\n");
435 Configuration::listParams(79, 14);
436 exit(1);
437}
438
439int main(int argc, char** argv)
440{
441 initStdIOLoggers();
442 LogWriter::setLogParams("*:stderr:30");
443
444 programName = argv[0];
445 Display* dpy;
446
447 for (int i = 1; i < argc; i++) {
448 if (Configuration::setParam(argv[i]))
449 continue;
450
451 if (argv[i][0] == '-') {
452 if (i+1 < argc) {
453 if (Configuration::setParam(&argv[i][1], argv[i+1])) {
454 i++;
455 continue;
456 }
457 }
458 usage();
459 }
460
461 usage();
462 }
463
464 CharArray dpyStr(displayname.getData());
465 if (!(dpy = XOpenDisplay(dpyStr.buf[0] ? dpyStr.buf : 0))) {
466 fprintf(stderr,"%s: unable to open display \"%s\"\r\n",
467 programName, XDisplayName(displayname.getData()));
468 exit(1);
469 }
470
471 signal(SIGHUP, CleanupSignalHandler);
472 signal(SIGINT, CleanupSignalHandler);
473 signal(SIGTERM, CleanupSignalHandler);
474
475 try {
476 TXWindow::init(dpy,"x0vncserver");
477 Geometry geo(DisplayWidth(dpy, DefaultScreen(dpy)),
478 DisplayHeight(dpy, DefaultScreen(dpy)));
479 XDesktop desktop(dpy, &geo);
480 VNCServerST server("x0vncserver", &desktop);
481 QueryConnHandler qcHandler(dpy, &server);
482 server.setQueryConnectionHandler(&qcHandler);
483
484 TcpListener listener((int)rfbport);
485 vlog.info("Listening on port %d", (int)rfbport);
486
487 FileTcpFilter fileTcpFilter(hostsFile.getData());
488 if (strlen(hostsFile.getData()) != 0)
489 listener.setFilter(&fileTcpFilter);
490
491 PollingScheduler sched((int)pollingCycle, (int)maxProcessorUsage);
492
493 while (!caughtSignal) {
494 struct timeval tv;
495 fd_set rfds;
496 std::list<Socket*> sockets;
497 std::list<Socket*>::iterator i;
498
499 // Process any incoming X events
500 TXWindow::handleXEvents(dpy);
501
502 FD_ZERO(&rfds);
503 FD_SET(listener.getFd(), &rfds);
504 server.getSockets(&sockets);
505 int clients_connected = 0;
506 for (i = sockets.begin(); i != sockets.end(); i++) {
507 if ((*i)->isShutdown()) {
508 server.removeSocket(*i);
509 delete (*i);
510 } else {
511 FD_SET((*i)->getFd(), &rfds);
512 clients_connected++;
513 }
514 }
515
516 if (clients_connected) {
517 int wait_ms = sched.millisRemaining();
518 if (wait_ms > 500) {
519 wait_ms = 500;
520 }
521 tv.tv_usec = wait_ms * 1000;
522#ifdef DEBUG
523 // fprintf(stderr, "[%d]\t", wait_ms);
524#endif
525 } else {
526 sched.reset();
527 tv.tv_usec = 100000;
528 }
529 tv.tv_sec = 0;
530
531 // Do the wait...
532 sched.sleepStarted();
533 int n = select(FD_SETSIZE, &rfds, 0, 0, &tv);
534 sched.sleepFinished();
535
536 if (n < 0) {
537 if (errno == EINTR) {
538 vlog.debug("Interrupted select() system call");
539 continue;
540 } else {
541 throw rdr::SystemException("select", errno);
542 }
543 }
544
545 // Accept new VNC connections
546 if (FD_ISSET(listener.getFd(), &rfds)) {
547 Socket* sock = listener.accept();
548 if (sock) {
549 server.addSocket(sock);
550 } else {
551 vlog.status("Client connection rejected");
552 }
553 }
554
555 Timer::checkTimeouts();
556 server.checkTimeouts();
557
558 // Client list could have been changed.
559 server.getSockets(&sockets);
560
561 // Nothing more to do if there are no client connections.
562 if (sockets.empty())
563 continue;
564
565 // Process events on existing VNC connections
566 for (i = sockets.begin(); i != sockets.end(); i++) {
567 if (FD_ISSET((*i)->getFd(), &rfds))
568 server.processSocketEvent(*i);
569 }
570
571 if (desktop.isRunning() && sched.goodTimeToPoll()) {
572 sched.newPass();
573 desktop.poll();
574 }
575 }
576
577 } catch (rdr::Exception &e) {
578 vlog.error(e.str());
579 return 1;
580 }
581
582 vlog.info("Terminated");
583 return 0;
584}