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 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
  2. * Copyright (C) 2004-2008 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/Timer.h>
  31. #include <network/TcpSocket.h>
  32. #include <network/UnixSocket.h>
  33. #include <vncconfig/QueryConnectDialog.h>
  34. #include <signal.h>
  35. #include <X11/X.h>
  36. #include <X11/Xlib.h>
  37. #include <X11/Xutil.h>
  38. #include <x0vncserver/XDesktop.h>
  39. #include <x0vncserver/Geometry.h>
  40. #include <x0vncserver/Image.h>
  41. #include <x0vncserver/PollingScheduler.h>
  42. extern char buildtime[];
  43. using namespace rfb;
  44. using namespace network;
  45. static LogWriter vlog("Main");
  46. IntParameter pollingCycle("PollingCycle", "Milliseconds per one polling "
  47. "cycle; actual interval may be dynamically "
  48. "adjusted to satisfy MaxProcessorUsage setting", 30);
  49. IntParameter maxProcessorUsage("MaxProcessorUsage", "Maximum percentage of "
  50. "CPU time to be consumed", 35);
  51. StringParameter displayname("display", "The X display", "");
  52. IntParameter rfbport("rfbport", "TCP port to listen for RFB protocol",5900);
  53. StringParameter rfbunixpath("rfbunixpath", "Unix socket to listen for RFB protocol", "");
  54. IntParameter rfbunixmode("rfbunixmode", "Unix socket access mode", 0600);
  55. IntParameter queryConnectTimeout("QueryConnectTimeout",
  56. "Number of seconds to show the Accept Connection dialog before "
  57. "rejecting the connection",
  58. 10);
  59. StringParameter hostsFile("HostsFile", "File with IP access control rules", "");
  60. //
  61. // Allow the main loop terminate itself gracefully on receiving a signal.
  62. //
  63. static bool caughtSignal = false;
  64. static void CleanupSignalHandler(int sig)
  65. {
  66. caughtSignal = true;
  67. }
  68. class QueryConnHandler : public VNCServerST::QueryConnectionHandler,
  69. public QueryResultCallback {
  70. public:
  71. QueryConnHandler(Display* dpy, VNCServerST* vs)
  72. : display(dpy), server(vs), queryConnectDialog(0), queryConnectSock(0) {}
  73. ~QueryConnHandler() { delete queryConnectDialog; }
  74. // -=- VNCServerST::QueryConnectionHandler interface
  75. virtual VNCServerST::queryResult queryConnection(network::Socket* sock,
  76. const char* userName,
  77. char** reason) {
  78. if (queryConnectSock) {
  79. *reason = strDup("Another connection is currently being queried.");
  80. return VNCServerST::REJECT;
  81. }
  82. if (!userName) userName = "(anonymous)";
  83. queryConnectSock = sock;
  84. CharArray address(sock->getPeerAddress());
  85. delete queryConnectDialog;
  86. queryConnectDialog = new QueryConnectDialog(display, address.buf,
  87. userName, queryConnectTimeout,
  88. this);
  89. queryConnectDialog->map();
  90. return VNCServerST::PENDING;
  91. }
  92. // -=- QueryResultCallback interface
  93. virtual void queryApproved() {
  94. server->approveConnection(queryConnectSock, true, 0);
  95. queryConnectSock = 0;
  96. }
  97. virtual void queryRejected() {
  98. server->approveConnection(queryConnectSock, false,
  99. "Connection rejected by local user");
  100. queryConnectSock = 0;
  101. }
  102. private:
  103. Display* display;
  104. VNCServerST* server;
  105. QueryConnectDialog* queryConnectDialog;
  106. network::Socket* queryConnectSock;
  107. };
  108. class FileTcpFilter : public TcpFilter
  109. {
  110. public:
  111. FileTcpFilter(const char *fname)
  112. : TcpFilter("-"), fileName(NULL), lastModTime(0)
  113. {
  114. if (fname != NULL)
  115. fileName = strdup((char *)fname);
  116. }
  117. virtual ~FileTcpFilter()
  118. {
  119. if (fileName != NULL)
  120. free(fileName);
  121. }
  122. virtual bool verifyConnection(Socket* s)
  123. {
  124. if (!reloadRules()) {
  125. vlog.error("Could not read IP filtering rules: rejecting all clients");
  126. filter.clear();
  127. filter.push_back(parsePattern("-"));
  128. return false;
  129. }
  130. return TcpFilter::verifyConnection(s);
  131. }
  132. protected:
  133. bool reloadRules()
  134. {
  135. if (fileName == NULL)
  136. return true;
  137. struct stat st;
  138. if (stat(fileName, &st) != 0)
  139. return false;
  140. if (st.st_mtime != lastModTime) {
  141. // Actually reload only if the file was modified
  142. FILE *fp = fopen(fileName, "r");
  143. if (fp == NULL)
  144. return false;
  145. // Remove all the rules from the parent class
  146. filter.clear();
  147. // Parse the file contents adding rules to the parent class
  148. char buf[32];
  149. while (readLine(buf, 32, fp)) {
  150. if (buf[0] && strchr("+-?", buf[0])) {
  151. filter.push_back(parsePattern(buf));
  152. }
  153. }
  154. fclose(fp);
  155. lastModTime = st.st_mtime;
  156. }
  157. return true;
  158. }
  159. protected:
  160. char *fileName;
  161. time_t lastModTime;
  162. private:
  163. //
  164. // NOTE: we silently truncate long lines in this function.
  165. //
  166. bool readLine(char *buf, int bufSize, FILE *fp)
  167. {
  168. if (fp == NULL || buf == NULL || bufSize == 0)
  169. return false;
  170. if (fgets(buf, bufSize, fp) == NULL)
  171. return false;
  172. char *ptr = strchr(buf, '\n');
  173. if (ptr != NULL) {
  174. *ptr = '\0'; // remove newline at the end
  175. } else {
  176. if (!feof(fp)) {
  177. int c;
  178. do { // skip the rest of a long line
  179. c = getc(fp);
  180. } while (c != '\n' && c != EOF);
  181. }
  182. }
  183. return true;
  184. }
  185. };
  186. char* programName;
  187. static void printVersion(FILE *fp)
  188. {
  189. fprintf(fp, "TigerVNC Server version %s, built %s\n",
  190. PACKAGE_VERSION, buildtime);
  191. }
  192. static void usage()
  193. {
  194. printVersion(stderr);
  195. fprintf(stderr, "\nUsage: %s [<parameters>]\n", programName);
  196. fprintf(stderr, " %s --version\n", programName);
  197. fprintf(stderr,"\n"
  198. "Parameters can be turned on with -<param> or off with -<param>=0\n"
  199. "Parameters which take a value can be specified as "
  200. "-<param> <value>\n"
  201. "Other valid forms are <param>=<value> -<param>=<value> "
  202. "--<param>=<value>\n"
  203. "Parameter names are case-insensitive. The parameters are:\n\n");
  204. Configuration::listParams(79, 14);
  205. exit(1);
  206. }
  207. int main(int argc, char** argv)
  208. {
  209. initStdIOLoggers();
  210. LogWriter::setLogParams("*:stderr:30");
  211. programName = argv[0];
  212. Display* dpy;
  213. Configuration::enableServerParams();
  214. // Disable configuration parameters which we do not support
  215. Configuration::removeParam("AcceptSetDesktopSize");
  216. for (int i = 1; i < argc; i++) {
  217. if (Configuration::setParam(argv[i]))
  218. continue;
  219. if (argv[i][0] == '-') {
  220. if (i+1 < argc) {
  221. if (Configuration::setParam(&argv[i][1], argv[i+1])) {
  222. i++;
  223. continue;
  224. }
  225. }
  226. if (strcmp(argv[i], "-v") == 0 ||
  227. strcmp(argv[i], "-version") == 0 ||
  228. strcmp(argv[i], "--version") == 0) {
  229. printVersion(stdout);
  230. return 0;
  231. }
  232. usage();
  233. }
  234. usage();
  235. }
  236. CharArray dpyStr(displayname.getData());
  237. if (!(dpy = XOpenDisplay(dpyStr.buf[0] ? dpyStr.buf : 0))) {
  238. // FIXME: Why not vlog.error(...)?
  239. fprintf(stderr,"%s: unable to open display \"%s\"\r\n",
  240. programName, XDisplayName(dpyStr.buf));
  241. exit(1);
  242. }
  243. signal(SIGHUP, CleanupSignalHandler);
  244. signal(SIGINT, CleanupSignalHandler);
  245. signal(SIGTERM, CleanupSignalHandler);
  246. std::list<SocketListener*> listeners;
  247. try {
  248. TXWindow::init(dpy,"x0vncserver");
  249. Geometry geo(DisplayWidth(dpy, DefaultScreen(dpy)),
  250. DisplayHeight(dpy, DefaultScreen(dpy)));
  251. if (geo.getRect().is_empty()) {
  252. vlog.error("Exiting with error");
  253. return 1;
  254. }
  255. XDesktop desktop(dpy, &geo);
  256. VNCServerST server("x0vncserver", &desktop);
  257. QueryConnHandler qcHandler(dpy, &server);
  258. server.setQueryConnectionHandler(&qcHandler);
  259. if (rfbunixpath.getValueStr()[0] != '\0') {
  260. listeners.push_back(new network::UnixListener(rfbunixpath, rfbunixmode));
  261. vlog.info("Listening on %s (mode %04o)", (const char*)rfbunixpath, (int)rfbunixmode);
  262. } else {
  263. createTcpListeners(&listeners, 0, (int)rfbport);
  264. vlog.info("Listening on port %d", (int)rfbport);
  265. }
  266. const char *hostsData = hostsFile.getData();
  267. FileTcpFilter fileTcpFilter(hostsData);
  268. if (strlen(hostsData) != 0)
  269. for (std::list<SocketListener*>::iterator i = listeners.begin();
  270. i != listeners.end();
  271. i++)
  272. (*i)->setFilter(&fileTcpFilter);
  273. delete[] hostsData;
  274. PollingScheduler sched((int)pollingCycle, (int)maxProcessorUsage);
  275. while (!caughtSignal) {
  276. int wait_ms;
  277. struct timeval tv;
  278. fd_set rfds, wfds;
  279. std::list<Socket*> sockets;
  280. std::list<Socket*>::iterator i;
  281. // Process any incoming X events
  282. TXWindow::handleXEvents(dpy);
  283. FD_ZERO(&rfds);
  284. FD_ZERO(&wfds);
  285. FD_SET(ConnectionNumber(dpy), &rfds);
  286. for (std::list<SocketListener*>::iterator i = listeners.begin();
  287. i != listeners.end();
  288. i++)
  289. FD_SET((*i)->getFd(), &rfds);
  290. server.getSockets(&sockets);
  291. int clients_connected = 0;
  292. for (i = sockets.begin(); i != sockets.end(); i++) {
  293. if ((*i)->isShutdown()) {
  294. server.removeSocket(*i);
  295. delete (*i);
  296. } else {
  297. FD_SET((*i)->getFd(), &rfds);
  298. if ((*i)->outStream().bufferUsage() > 0)
  299. FD_SET((*i)->getFd(), &wfds);
  300. clients_connected++;
  301. }
  302. }
  303. if (!clients_connected)
  304. sched.reset();
  305. wait_ms = 0;
  306. if (sched.isRunning()) {
  307. wait_ms = sched.millisRemaining();
  308. if (wait_ms > 500) {
  309. wait_ms = 500;
  310. }
  311. }
  312. soonestTimeout(&wait_ms, server.checkTimeouts());
  313. tv.tv_sec = wait_ms / 1000;
  314. tv.tv_usec = (wait_ms % 1000) * 1000;
  315. // Do the wait...
  316. sched.sleepStarted();
  317. int n = select(FD_SETSIZE, &rfds, &wfds, 0,
  318. wait_ms ? &tv : NULL);
  319. sched.sleepFinished();
  320. if (n < 0) {
  321. if (errno == EINTR) {
  322. vlog.debug("Interrupted select() system call");
  323. continue;
  324. } else {
  325. throw rdr::SystemException("select", errno);
  326. }
  327. }
  328. // Accept new VNC connections
  329. for (std::list<SocketListener*>::iterator i = listeners.begin();
  330. i != listeners.end();
  331. i++) {
  332. if (FD_ISSET((*i)->getFd(), &rfds)) {
  333. Socket* sock = (*i)->accept();
  334. if (sock) {
  335. sock->outStream().setBlocking(false);
  336. server.addSocket(sock);
  337. } else {
  338. vlog.status("Client connection rejected");
  339. }
  340. }
  341. }
  342. server.checkTimeouts();
  343. // Client list could have been changed.
  344. server.getSockets(&sockets);
  345. // Nothing more to do if there are no client connections.
  346. if (sockets.empty())
  347. continue;
  348. // Process events on existing VNC connections
  349. for (i = sockets.begin(); i != sockets.end(); i++) {
  350. if (FD_ISSET((*i)->getFd(), &rfds))
  351. server.processSocketReadEvent(*i);
  352. if (FD_ISSET((*i)->getFd(), &wfds))
  353. server.processSocketWriteEvent(*i);
  354. }
  355. if (desktop.isRunning() && sched.goodTimeToPoll()) {
  356. sched.newPass();
  357. desktop.poll();
  358. }
  359. }
  360. } catch (rdr::Exception &e) {
  361. vlog.error("%s", e.str());
  362. return 1;
  363. }
  364. TXWindow::handleXEvents(dpy);
  365. vlog.info("Terminated");
  366. return 0;
  367. }