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