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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  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. #ifdef HAVE_CONFIG_H
  22. #include <config.h>
  23. #endif
  24. #include <strings.h>
  25. #include <sys/types.h>
  26. #include <sys/stat.h>
  27. #include <unistd.h>
  28. #include <errno.h>
  29. #include <pwd.h>
  30. #include <rfb/Logger_stdio.h>
  31. #include <rfb/LogWriter.h>
  32. #include <rfb/VNCServerST.h>
  33. #include <rfb/Configuration.h>
  34. #include <rfb/Timer.h>
  35. #include <network/TcpSocket.h>
  36. #include <network/UnixSocket.h>
  37. #ifdef HAVE_LIBSYSTEMD
  38. # include <systemd/sd-daemon.h>
  39. #endif
  40. #include <signal.h>
  41. #include <X11/X.h>
  42. #include <X11/Xlib.h>
  43. #include <X11/Xutil.h>
  44. #include <x0vncserver/XDesktop.h>
  45. #include <x0vncserver/Geometry.h>
  46. #include <x0vncserver/Image.h>
  47. #include <x0vncserver/PollingScheduler.h>
  48. extern char buildtime[];
  49. using namespace rfb;
  50. using namespace network;
  51. static LogWriter vlog("Main");
  52. static const char* defaultDesktopName();
  53. IntParameter pollingCycle("PollingCycle", "Milliseconds per one polling "
  54. "cycle; actual interval may be dynamically "
  55. "adjusted to satisfy MaxProcessorUsage setting", 30);
  56. IntParameter maxProcessorUsage("MaxProcessorUsage", "Maximum percentage of "
  57. "CPU time to be consumed", 35);
  58. StringParameter desktopName("desktop", "Name of VNC desktop", defaultDesktopName());
  59. StringParameter displayname("display", "The X display", "");
  60. IntParameter rfbport("rfbport", "TCP port to listen for RFB protocol",5900);
  61. StringParameter rfbunixpath("rfbunixpath", "Unix socket to listen for RFB protocol", "");
  62. IntParameter rfbunixmode("rfbunixmode", "Unix socket access mode", 0600);
  63. StringParameter hostsFile("HostsFile", "File with IP access control rules", "");
  64. BoolParameter localhostOnly("localhost",
  65. "Only allow connections from localhost",
  66. false);
  67. StringParameter interface("interface",
  68. "listen on the specified network address",
  69. "all");
  70. static const char* defaultDesktopName()
  71. {
  72. long host_max = sysconf(_SC_HOST_NAME_MAX);
  73. if (host_max < 0)
  74. return "";
  75. std::vector<char> hostname(host_max + 1);
  76. if (gethostname(hostname.data(), hostname.size()) == -1)
  77. return "";
  78. struct passwd* pwent = getpwuid(getuid());
  79. if (pwent == NULL)
  80. return "";
  81. int len = snprintf(NULL, 0, "%s@%s", pwent->pw_name, hostname.data());
  82. if (len < 0)
  83. return "";
  84. char* name = new char[len + 1];
  85. snprintf(name, len + 1, "%s@%s", pwent->pw_name, hostname.data());
  86. return name;
  87. }
  88. //
  89. // Allow the main loop terminate itself gracefully on receiving a signal.
  90. //
  91. static bool caughtSignal = false;
  92. static void CleanupSignalHandler(int /*sig*/)
  93. {
  94. caughtSignal = true;
  95. }
  96. static bool hasSystemdListeners()
  97. {
  98. #ifdef HAVE_LIBSYSTEMD
  99. // This also returns true on errors, because we then assume we were
  100. // meant to use systemd but failed somewhere
  101. return sd_listen_fds(0) != 0;
  102. #else
  103. return false;
  104. #endif
  105. }
  106. static int createSystemdListeners(std::list<SocketListener*> *listeners)
  107. {
  108. #ifdef HAVE_LIBSYSTEMD
  109. int count = sd_listen_fds(0);
  110. if (count < 0) {
  111. vlog.error("Error getting listening sockets from systemd: %s",
  112. strerror(-count));
  113. return count;
  114. }
  115. for (int i = 0; i < count; ++i)
  116. listeners->push_back(new TcpListener(SD_LISTEN_FDS_START + i));
  117. return count;
  118. #else
  119. (void)listeners;
  120. return 0;
  121. #endif
  122. }
  123. class FileTcpFilter : public TcpFilter
  124. {
  125. public:
  126. FileTcpFilter(const char *fname)
  127. : TcpFilter("-"), fileName(NULL), lastModTime(0)
  128. {
  129. if (fname != NULL)
  130. fileName = strdup((char *)fname);
  131. }
  132. virtual ~FileTcpFilter()
  133. {
  134. if (fileName != NULL)
  135. free(fileName);
  136. }
  137. virtual bool verifyConnection(Socket* s)
  138. {
  139. if (!reloadRules()) {
  140. vlog.error("Could not read IP filtering rules: rejecting all clients");
  141. filter.clear();
  142. filter.push_back(parsePattern("-"));
  143. return false;
  144. }
  145. return TcpFilter::verifyConnection(s);
  146. }
  147. protected:
  148. bool reloadRules()
  149. {
  150. if (fileName == NULL)
  151. return true;
  152. struct stat st;
  153. if (stat(fileName, &st) != 0)
  154. return false;
  155. if (st.st_mtime != lastModTime) {
  156. // Actually reload only if the file was modified
  157. FILE *fp = fopen(fileName, "r");
  158. if (fp == NULL)
  159. return false;
  160. // Remove all the rules from the parent class
  161. filter.clear();
  162. // Parse the file contents adding rules to the parent class
  163. char buf[32];
  164. while (readLine(buf, 32, fp)) {
  165. if (buf[0] && strchr("+-?", buf[0])) {
  166. filter.push_back(parsePattern(buf));
  167. }
  168. }
  169. fclose(fp);
  170. lastModTime = st.st_mtime;
  171. }
  172. return true;
  173. }
  174. protected:
  175. char *fileName;
  176. time_t lastModTime;
  177. private:
  178. //
  179. // NOTE: we silently truncate long lines in this function.
  180. //
  181. bool readLine(char *buf, int bufSize, FILE *fp)
  182. {
  183. if (fp == NULL || buf == NULL || bufSize == 0)
  184. return false;
  185. if (fgets(buf, bufSize, fp) == NULL)
  186. return false;
  187. char *ptr = strchr(buf, '\n');
  188. if (ptr != NULL) {
  189. *ptr = '\0'; // remove newline at the end
  190. } else {
  191. if (!feof(fp)) {
  192. int c;
  193. do { // skip the rest of a long line
  194. c = getc(fp);
  195. } while (c != '\n' && c != EOF);
  196. }
  197. }
  198. return true;
  199. }
  200. };
  201. char* programName;
  202. static void printVersion(FILE *fp)
  203. {
  204. fprintf(fp, "TigerVNC Server version %s, built %s\n",
  205. PACKAGE_VERSION, buildtime);
  206. }
  207. static void usage()
  208. {
  209. printVersion(stderr);
  210. fprintf(stderr, "\nUsage: %s [<parameters>]\n", programName);
  211. fprintf(stderr, " %s --version\n", programName);
  212. fprintf(stderr,"\n"
  213. "Parameters can be turned on with -<param> or off with -<param>=0\n"
  214. "Parameters which take a value can be specified as "
  215. "-<param> <value>\n"
  216. "Other valid forms are <param>=<value> -<param>=<value> "
  217. "--<param>=<value>\n"
  218. "Parameter names are case-insensitive. The parameters are:\n\n");
  219. Configuration::listParams(79, 14);
  220. exit(1);
  221. }
  222. int main(int argc, char** argv)
  223. {
  224. initStdIOLoggers();
  225. LogWriter::setLogParams("*:stderr:30");
  226. programName = argv[0];
  227. Display* dpy;
  228. Configuration::enableServerParams();
  229. // FIXME: We don't support clipboard yet
  230. Configuration::removeParam("AcceptCutText");
  231. Configuration::removeParam("SendCutText");
  232. Configuration::removeParam("MaxCutText");
  233. // Assume different defaults when socket activated
  234. if (hasSystemdListeners())
  235. rfbport.setParam(-1);
  236. for (int i = 1; i < argc; i++) {
  237. if (Configuration::setParam(argv[i]))
  238. continue;
  239. if (argv[i][0] == '-') {
  240. if (i+1 < argc) {
  241. if (Configuration::setParam(&argv[i][1], argv[i+1])) {
  242. i++;
  243. continue;
  244. }
  245. }
  246. if (strcmp(argv[i], "-v") == 0 ||
  247. strcmp(argv[i], "-version") == 0 ||
  248. strcmp(argv[i], "--version") == 0) {
  249. printVersion(stdout);
  250. return 0;
  251. }
  252. usage();
  253. }
  254. usage();
  255. }
  256. if (!(dpy = XOpenDisplay(displayname))) {
  257. // FIXME: Why not vlog.error(...)?
  258. fprintf(stderr,"%s: unable to open display \"%s\"\r\n",
  259. programName, XDisplayName(displayname));
  260. exit(1);
  261. }
  262. signal(SIGHUP, CleanupSignalHandler);
  263. signal(SIGINT, CleanupSignalHandler);
  264. signal(SIGTERM, CleanupSignalHandler);
  265. std::list<SocketListener*> listeners;
  266. try {
  267. TXWindow::init(dpy,"x0vncserver");
  268. Geometry geo(DisplayWidth(dpy, DefaultScreen(dpy)),
  269. DisplayHeight(dpy, DefaultScreen(dpy)));
  270. if (geo.getRect().is_empty()) {
  271. vlog.error("Exiting with error");
  272. return 1;
  273. }
  274. XDesktop desktop(dpy, &geo);
  275. VNCServerST server(desktopName, &desktop);
  276. if (createSystemdListeners(&listeners) > 0) {
  277. // When systemd is in charge of listeners, do not listen to anything else
  278. vlog.info("Listening on systemd sockets");
  279. }
  280. if (rfbunixpath.getValueStr()[0] != '\0') {
  281. listeners.push_back(new network::UnixListener(rfbunixpath, rfbunixmode));
  282. vlog.info("Listening on %s (mode %04o)", (const char*)rfbunixpath, (int)rfbunixmode);
  283. }
  284. if ((int)rfbport != -1) {
  285. std::list<network::SocketListener*> tcp_listeners;
  286. const char *addr = interface;
  287. if (strcasecmp(addr, "all") == 0)
  288. addr = 0;
  289. if (localhostOnly)
  290. createLocalTcpListeners(&tcp_listeners, (int)rfbport);
  291. else
  292. createTcpListeners(&tcp_listeners, addr, (int)rfbport);
  293. if (!tcp_listeners.empty()) {
  294. listeners.splice (listeners.end(), tcp_listeners);
  295. vlog.info("Listening for VNC connections on %s interface(s), port %d",
  296. localhostOnly ? "local" : (const char*)interface,
  297. (int)rfbport);
  298. }
  299. FileTcpFilter fileTcpFilter(hostsFile);
  300. if (strlen(hostsFile) != 0)
  301. for (std::list<SocketListener*>::iterator i = listeners.begin();
  302. i != listeners.end();
  303. i++)
  304. (*i)->setFilter(&fileTcpFilter);
  305. }
  306. if (listeners.empty()) {
  307. vlog.error("No path or port configured for incoming connections");
  308. return -1;
  309. }
  310. PollingScheduler sched((int)pollingCycle, (int)maxProcessorUsage);
  311. while (!caughtSignal) {
  312. int wait_ms;
  313. struct timeval tv;
  314. fd_set rfds, wfds;
  315. std::list<Socket*> sockets;
  316. std::list<Socket*>::iterator i;
  317. // Process any incoming X events
  318. TXWindow::handleXEvents(dpy);
  319. FD_ZERO(&rfds);
  320. FD_ZERO(&wfds);
  321. FD_SET(ConnectionNumber(dpy), &rfds);
  322. for (std::list<SocketListener*>::iterator i = listeners.begin();
  323. i != listeners.end();
  324. i++)
  325. FD_SET((*i)->getFd(), &rfds);
  326. server.getSockets(&sockets);
  327. int clients_connected = 0;
  328. for (i = sockets.begin(); i != sockets.end(); i++) {
  329. if ((*i)->isShutdown()) {
  330. server.removeSocket(*i);
  331. delete (*i);
  332. } else {
  333. FD_SET((*i)->getFd(), &rfds);
  334. if ((*i)->outStream().hasBufferedData())
  335. FD_SET((*i)->getFd(), &wfds);
  336. clients_connected++;
  337. }
  338. }
  339. if (!clients_connected)
  340. sched.reset();
  341. wait_ms = 0;
  342. if (sched.isRunning()) {
  343. wait_ms = sched.millisRemaining();
  344. if (wait_ms > 500) {
  345. wait_ms = 500;
  346. }
  347. }
  348. soonestTimeout(&wait_ms, Timer::checkTimeouts());
  349. tv.tv_sec = wait_ms / 1000;
  350. tv.tv_usec = (wait_ms % 1000) * 1000;
  351. // Do the wait...
  352. sched.sleepStarted();
  353. int n = select(FD_SETSIZE, &rfds, &wfds, 0,
  354. wait_ms ? &tv : NULL);
  355. sched.sleepFinished();
  356. if (n < 0) {
  357. if (errno == EINTR) {
  358. vlog.debug("Interrupted select() system call");
  359. continue;
  360. } else {
  361. throw rdr::SystemException("select", errno);
  362. }
  363. }
  364. // Accept new VNC connections
  365. for (std::list<SocketListener*>::iterator i = listeners.begin();
  366. i != listeners.end();
  367. i++) {
  368. if (FD_ISSET((*i)->getFd(), &rfds)) {
  369. Socket* sock = (*i)->accept();
  370. if (sock) {
  371. server.addSocket(sock);
  372. } else {
  373. vlog.status("Client connection rejected");
  374. }
  375. }
  376. }
  377. Timer::checkTimeouts();
  378. // Client list could have been changed.
  379. server.getSockets(&sockets);
  380. // Nothing more to do if there are no client connections.
  381. if (sockets.empty())
  382. continue;
  383. // Process events on existing VNC connections
  384. for (i = sockets.begin(); i != sockets.end(); i++) {
  385. if (FD_ISSET((*i)->getFd(), &rfds))
  386. server.processSocketReadEvent(*i);
  387. if (FD_ISSET((*i)->getFd(), &wfds))
  388. server.processSocketWriteEvent(*i);
  389. }
  390. if (desktop.isRunning() && sched.goodTimeToPoll()) {
  391. sched.newPass();
  392. desktop.poll();
  393. }
  394. }
  395. } catch (rdr::Exception &e) {
  396. vlog.error("%s", e.str());
  397. return 1;
  398. }
  399. TXWindow::handleXEvents(dpy);
  400. // Run listener destructors; remove UNIX sockets etc
  401. for (std::list<SocketListener*>::iterator i = listeners.begin();
  402. i != listeners.end();
  403. i++) {
  404. delete *i;
  405. }
  406. vlog.info("Terminated");
  407. return 0;
  408. }