blob: ac65c0bef5a9ad3f542b9ce610640111d8a59a97 [file] [log] [blame]
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +00001/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved.
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +00002 * Copyright (C) 2004-2005 Constantin Kaplinsky. All Rights Reserved.
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +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 */
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000019
20// FIXME: Check cases when screen width/height is not a multiply of 32.
21// e.g. 800x600.
22
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000023#include <strings.h>
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000024#include <sys/types.h>
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000025#include <sys/stat.h>
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000026#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
34#include <network/TcpSocket.h>
35
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000036#include <signal.h>
37#include <X11/X.h>
38#include <X11/Xlib.h>
39#include <X11/Xutil.h>
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000040#ifdef HAVE_XTEST
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000041#include <X11/extensions/XTest.h>
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000042#endif
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000043
Constantin Kaplinskyaf1891c2005-09-29 06:18:28 +000044#include <x0vncserver/Image.h>
45#include <x0vncserver/PollingManager.h>
Constantin Kaplinsky0cbad332006-02-16 14:51:11 +000046#include <x0vncserver/PollingScheduler.h>
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000047
Constantin Kaplinskya6866902006-03-02 12:03:30 +000048// XXX Lynx/OS 2.3: protos for select(), bzero()
49#ifdef Lynx
50#include <sys/proto.h>
51#endif
52
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000053using namespace rfb;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000054using namespace network;
55
56LogWriter vlog("main");
57
Constantin Kaplinsky602f34d2005-09-14 16:11:41 +000058IntParameter pollingCycle("PollingCycle", "Milliseconds per one polling "
59 "cycle; actual interval may be dynamically "
Constantin Kaplinskyef7ac9b2006-02-10 12:26:54 +000060 "adjusted to satisfy MaxProcessorUsage setting", 30);
Constantin Kaplinsky602f34d2005-09-14 16:11:41 +000061IntParameter maxProcessorUsage("MaxProcessorUsage", "Maximum percentage of "
62 "CPU time to be consumed", 35);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000063BoolParameter useShm("UseSHM", "Use MIT-SHM extension if available", true);
64BoolParameter useOverlay("OverlayMode", "Use overlay mode under "
65 "IRIX or Solaris", true);
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000066StringParameter displayname("display", "The X display", "");
67IntParameter rfbport("rfbport", "TCP port to listen for RFB protocol",5900);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000068StringParameter hostsFile("HostsFile", "File with IP access control rules", "");
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000069VncAuthPasswdFileParameter vncAuthPasswdFile;
70
71static void CleanupSignalHandler(int sig)
72{
73 // CleanupSignalHandler allows C++ object cleanup to happen because it calls
74 // exit() rather than the default which is to abort.
75 fprintf(stderr,"CleanupSignalHandler called\n");
76 exit(1);
77}
78
79
80class XDesktop : public SDesktop, public rfb::ColourMap
81{
82public:
83 XDesktop(Display* dpy_)
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000084 : dpy(dpy_), pb(0), server(0), oldButtonMask(0), haveXtest(false),
Constantin Kaplinskyaf1891c2005-09-29 06:18:28 +000085 maxButtons(0)
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000086 {
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000087#ifdef HAVE_XTEST
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000088 int xtestEventBase;
89 int xtestErrorBase;
90 int major, minor;
91
92 if (XTestQueryExtension(dpy, &xtestEventBase,
93 &xtestErrorBase, &major, &minor)) {
94 XTestGrabControl(dpy, True);
95 vlog.info("XTest extension present - version %d.%d",major,minor);
96 haveXtest = true;
97 } else {
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +000098#endif
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000099 vlog.info("XTest extension not present");
100 vlog.info("unable to inject events or display while server is grabbed");
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000101#ifdef HAVE_XTEST
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000102 }
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000103#endif
104
105 // Determine actual number of buttons of the X pointer device.
106 unsigned char btnMap[8];
107 int numButtons = XGetPointerMapping(dpy, btnMap, 8);
108 maxButtons = (numButtons > 8) ? 8 : numButtons;
109 vlog.info("Enabling %d button%s of X pointer device",
110 maxButtons, (maxButtons != 1) ? "s" : "");
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000111
112 int dpyWidth = DisplayWidth(dpy, DefaultScreen(dpy));
113 int dpyHeight = DisplayHeight(dpy, DefaultScreen(dpy));
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000114
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000115 ImageFactory factory((bool)useShm, (bool)useOverlay);
116 image = factory.newImage(dpy, dpyWidth, dpyHeight);
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000117 image->get(DefaultRootWindow(dpy));
118
Constantin Kaplinskyaf1891c2005-09-29 06:18:28 +0000119 pollmgr = new PollingManager(dpy, image, &factory);
120
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000121 pf.bpp = image->xim->bits_per_pixel;
122 pf.depth = image->xim->depth;
123 pf.bigEndian = (image->xim->byte_order == MSBFirst);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000124 pf.trueColour = image->isTrueColor();
125 pf.redShift = ffs(image->xim->red_mask) - 1;
126 pf.greenShift = ffs(image->xim->green_mask) - 1;
127 pf.blueShift = ffs(image->xim->blue_mask) - 1;
128 pf.redMax = image->xim->red_mask >> pf.redShift;
129 pf.greenMax = image->xim->green_mask >> pf.greenShift;
130 pf.blueMax = image->xim->blue_mask >> pf.blueShift;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000131
132 pb = new FullFramePixelBuffer(pf, dpyWidth, dpyHeight,
133 (rdr::U8*)image->xim->data, this);
134 }
135 virtual ~XDesktop() {
136 delete pb;
Constantin Kaplinskyaf1891c2005-09-29 06:18:28 +0000137 delete pollmgr;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000138 }
139
140 void setVNCServer(VNCServer* s) {
141 server = s;
Constantin Kaplinskyaf1891c2005-09-29 06:18:28 +0000142 pollmgr->setVNCServer(s);
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000143 server->setPixelBuffer(pb);
144 }
145
Constantin Kaplinskyaf1891c2005-09-29 06:18:28 +0000146 inline void poll() {
147 pollmgr->poll();
148 }
149
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000150 // -=- SDesktop interface
151
152 virtual void pointerEvent(const Point& pos, rdr::U8 buttonMask) {
Constantin Kaplinskyce676c62006-02-08 13:36:58 +0000153 pollmgr->setPointerPos(pos);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000154#ifdef HAVE_XTEST
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000155 if (!haveXtest) return;
156 XTestFakeMotionEvent(dpy, DefaultScreen(dpy), pos.x, pos.y, CurrentTime);
157 if (buttonMask != oldButtonMask) {
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000158 for (int i = 0; i < maxButtons; i++) {
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000159 if ((buttonMask ^ oldButtonMask) & (1<<i)) {
160 if (buttonMask & (1<<i)) {
161 XTestFakeButtonEvent(dpy, i+1, True, CurrentTime);
162 } else {
163 XTestFakeButtonEvent(dpy, i+1, False, CurrentTime);
164 }
165 }
166 }
167 }
168 oldButtonMask = buttonMask;
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000169#endif
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000170 }
171
172 virtual void keyEvent(rdr::U32 key, bool down) {
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000173#ifdef HAVE_XTEST
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000174 if (!haveXtest) return;
175 int keycode = XKeysymToKeycode(dpy, key);
176 if (keycode)
177 XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000178#endif
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000179 }
180
181 virtual void clientCutText(const char* str, int len) {
182 }
183
184 virtual Point getFbSize() {
185 return Point(pb->width(), pb->height());
186 }
187
188 // rfb::ColourMap callbacks
189 virtual void lookup(int index, int* r, int* g, int* b) {
190 XColor xc;
191 xc.pixel = index;
192 if (index < DisplayCells(dpy,DefaultScreen(dpy))) {
193 XQueryColor(dpy, DefaultColormap(dpy,DefaultScreen(dpy)), &xc);
194 } else {
195 xc.red = xc.green = xc.blue = 0;
196 }
197 *r = xc.red;
198 *g = xc.green;
199 *b = xc.blue;
200 }
201
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000202protected:
203 Display* dpy;
204 PixelFormat pf;
205 PixelBuffer* pb;
206 VNCServer* server;
207 Image* image;
Constantin Kaplinskyaf1891c2005-09-29 06:18:28 +0000208 PollingManager* pollmgr;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000209 int oldButtonMask;
210 bool haveXtest;
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000211 int maxButtons;
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000212};
213
214
215class FileTcpFilter : public TcpFilter
216{
217
218public:
219
220 FileTcpFilter(const char *fname)
221 : TcpFilter("-"), fileName(NULL), lastModTime(0)
222 {
223 if (fname != NULL)
224 fileName = strdup((char *)fname);
225 }
226
227 virtual ~FileTcpFilter()
228 {
229 if (fileName != NULL)
230 free(fileName);
231 }
232
233 virtual bool verifyConnection(Socket* s)
234 {
235 if (!reloadRules()) {
236 vlog.error("Could not read IP filtering rules: rejecting all clients");
237 filter.clear();
238 filter.push_back(parsePattern("-"));
239 return false;
240 }
241
242 return TcpFilter::verifyConnection(s);
243 }
244
245protected:
246
247 bool reloadRules()
248 {
249 if (fileName == NULL)
250 return true;
251
252 struct stat st;
253 if (stat(fileName, &st) != 0)
254 return false;
255
256 if (st.st_mtime != lastModTime) {
257 // Actually reload only if the file was modified
258 FILE *fp = fopen(fileName, "r");
259 if (fp == NULL)
260 return false;
261
262 // Remove all the rules from the parent class
263 filter.clear();
264
265 // Parse the file contents adding rules to the parent class
266 char buf[32];
267 while (readLine(buf, 32, fp)) {
268 if (buf[0] && strchr("+-?", buf[0])) {
269 filter.push_back(parsePattern(buf));
270 }
271 }
272
273 fclose(fp);
274 lastModTime = st.st_mtime;
275 }
276 return true;
277 }
278
279protected:
280
281 char *fileName;
282 time_t lastModTime;
283
284private:
285
286 //
287 // NOTE: we silently truncate long lines in this function.
288 //
289
290 bool readLine(char *buf, int bufSize, FILE *fp)
291 {
292 if (fp == NULL || buf == NULL || bufSize == 0)
293 return false;
294
295 if (fgets(buf, bufSize, fp) == NULL)
296 return false;
297
298 char *ptr = strchr(buf, '\n');
299 if (ptr != NULL) {
300 *ptr = '\0'; // remove newline at the end
301 } else {
302 if (!feof(fp)) {
303 int c;
304 do { // skip the rest of a long line
305 c = getc(fp);
306 } while (c != '\n' && c != EOF);
307 }
308 }
309 return true;
310 }
311
312};
313
314
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000315char* programName;
316
317static void usage()
318{
319 fprintf(stderr, "\nusage: %s [<parameters>]\n", programName);
320 fprintf(stderr,"\n"
321 "Parameters can be turned on with -<param> or off with -<param>=0\n"
322 "Parameters which take a value can be specified as "
323 "-<param> <value>\n"
324 "Other valid forms are <param>=<value> -<param>=<value> "
325 "--<param>=<value>\n"
326 "Parameter names are case-insensitive. The parameters are:\n\n");
327 Configuration::listParams(79, 14);
328 exit(1);
329}
330
331int main(int argc, char** argv)
332{
333 initStdIOLoggers();
334 LogWriter::setLogParams("*:stderr:30");
335
336 programName = argv[0];
337 Display* dpy;
338
339 for (int i = 1; i < argc; i++) {
340 if (Configuration::setParam(argv[i]))
341 continue;
342
343 if (argv[i][0] == '-') {
344 if (i+1 < argc) {
345 if (Configuration::setParam(&argv[i][1], argv[i+1])) {
346 i++;
347 continue;
348 }
349 }
350 usage();
351 }
352
353 usage();
354 }
355
356 CharArray dpyStr(displayname.getData());
357 if (!(dpy = XOpenDisplay(dpyStr.buf[0] ? dpyStr.buf : 0))) {
358 fprintf(stderr,"%s: unable to open display \"%s\"\r\n",
359 programName, XDisplayName(displayname.getData()));
360 exit(1);
361 }
362
363 signal(SIGHUP, CleanupSignalHandler);
364 signal(SIGINT, CleanupSignalHandler);
365 signal(SIGTERM, CleanupSignalHandler);
366
367 try {
368 XDesktop desktop(dpy);
369 VNCServerST server("x0vncserver", &desktop);
370 desktop.setVNCServer(&server);
371
372 TcpSocket::initTcpSockets();
373 TcpListener listener((int)rfbport);
374 vlog.info("Listening on port %d", (int)rfbport);
375
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000376 FileTcpFilter fileTcpFilter(hostsFile.getData());
377 if (strlen(hostsFile.getData()) != 0)
378 listener.setFilter(&fileTcpFilter);
379
Constantin Kaplinskye179b5a2006-02-17 09:30:21 +0000380 PollingScheduler sched((int)pollingCycle, (int)maxProcessorUsage);
Constantin Kaplinsky602f34d2005-09-14 16:11:41 +0000381
Constantin Kaplinsky3f56fa72006-02-16 11:50:25 +0000382 fd_set rfds;
383 struct timeval tv;
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000384
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000385 while (true) {
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000386
387 FD_ZERO(&rfds);
388 FD_SET(listener.getFd(), &rfds);
389
390 std::list<Socket*> sockets;
391 server.getSockets(&sockets);
392 std::list<Socket*>::iterator i;
Constantin Kaplinsky3f56fa72006-02-16 11:50:25 +0000393 int clients_connected = 0;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000394 for (i = sockets.begin(); i != sockets.end(); i++) {
395 FD_SET((*i)->getFd(), &rfds);
Constantin Kaplinsky3f56fa72006-02-16 11:50:25 +0000396 clients_connected++;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000397 }
398
Constantin Kaplinsky3f56fa72006-02-16 11:50:25 +0000399 if (clients_connected) {
Constantin Kaplinsky0cbad332006-02-16 14:51:11 +0000400 int wait_ms = sched.millisRemaining();
401 if (wait_ms > 500) {
Constantin Kaplinsky3f56fa72006-02-16 11:50:25 +0000402 wait_ms = 500;
403 }
404 tv.tv_usec = wait_ms * 1000;
405#ifdef DEBUG
Constantin Kaplinsky6a34ca52006-02-17 11:29:17 +0000406 // fprintf(stderr, "[%d]\t", wait_ms);
Constantin Kaplinsky3f56fa72006-02-16 11:50:25 +0000407#endif
408 } else {
Constantin Kaplinsky0cbad332006-02-16 14:51:11 +0000409 sched.reset();
410 tv.tv_usec = 100000;
Constantin Kaplinsky3f56fa72006-02-16 11:50:25 +0000411 }
412 tv.tv_sec = 0;
413
Constantin Kaplinskye179b5a2006-02-17 09:30:21 +0000414 sched.sleepStarted();
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000415 int n = select(FD_SETSIZE, &rfds, 0, 0, &tv);
Constantin Kaplinskye179b5a2006-02-17 09:30:21 +0000416 sched.sleepFinished();
417
Constantin Kaplinsky659e9002005-09-09 08:32:02 +0000418 if (n < 0) {
419 if (errno == EINTR) {
420 vlog.debug("interrupted select() system call");
421 continue;
422 } else {
423 throw rdr::SystemException("select", errno);
424 }
425 }
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000426
427 if (FD_ISSET(listener.getFd(), &rfds)) {
428 Socket* sock = listener.accept();
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000429 if (sock) {
430 server.addClient(sock);
431 } else {
432 vlog.status("Client connection rejected");
433 }
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000434 }
435
436 server.getSockets(&sockets);
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000437
438 // Nothing more to do if there are no client connections.
439 if (sockets.empty())
440 continue;
441
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000442 for (i = sockets.begin(); i != sockets.end(); i++) {
443 if (FD_ISSET((*i)->getFd(), &rfds)) {
444 server.processSocketEvent(*i);
445 }
446 }
447
448 server.checkTimeouts();
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000449
Constantin Kaplinskye47123e2006-02-16 16:44:50 +0000450 if (sched.goodTimeToPoll()) {
Constantin Kaplinsky0cbad332006-02-16 14:51:11 +0000451 sched.newPass();
Constantin Kaplinskyd31fd312005-09-08 19:29:02 +0000452 desktop.poll();
453 }
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000454 }
455
456 } catch (rdr::Exception &e) {
457 vlog.error(e.str());
458 };
459
460 return 0;
461}