You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

x0vncserver.cxx 15KB

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