blob: c406e3c9e778101e3a937017101ed4222aeeb3ff [file] [log] [blame]
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
Constantin Kaplinsky2c019832008-05-30 11:02:04 +00002 * Copyright (C) 2004-2008 Constantin Kaplinsky. All Rights Reserved.
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +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 */
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>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000032#include <rfb/Timer.h>
33#include <network/TcpSocket.h>
Pierre Ossman39594b82018-05-04 15:40:22 +020034#include <network/UnixSocket.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000035
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000036#include <signal.h>
37#include <X11/X.h>
38#include <X11/Xlib.h>
39#include <X11/Xutil.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000040
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020041#include <x0vncserver/XDesktop.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000042#include <x0vncserver/Geometry.h>
43#include <x0vncserver/Image.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000044#include <x0vncserver/PollingScheduler.h>
45
Constantin Kaplinskyb9632702006-12-01 10:54:55 +000046extern char buildtime[];
47
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000048using namespace rfb;
49using namespace network;
50
51static LogWriter vlog("Main");
52
53IntParameter pollingCycle("PollingCycle", "Milliseconds per one polling "
54 "cycle; actual interval may be dynamically "
55 "adjusted to satisfy MaxProcessorUsage setting", 30);
56IntParameter maxProcessorUsage("MaxProcessorUsage", "Maximum percentage of "
57 "CPU time to be consumed", 35);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000058StringParameter displayname("display", "The X display", "");
59IntParameter rfbport("rfbport", "TCP port to listen for RFB protocol",5900);
Pierre Ossman39594b82018-05-04 15:40:22 +020060StringParameter rfbunixpath("rfbunixpath", "Unix socket to listen for RFB protocol", "");
61IntParameter rfbunixmode("rfbunixmode", "Unix socket access mode", 0600);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000062StringParameter hostsFile("HostsFile", "File with IP access control rules", "");
Pierre Ossmanab22bc02019-01-16 16:35:32 +010063BoolParameter localhostOnly("localhost",
64 "Only allow connections from localhost",
65 false);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000066
67//
68// Allow the main loop terminate itself gracefully on receiving a signal.
69//
70
71static bool caughtSignal = false;
72
73static void CleanupSignalHandler(int sig)
74{
75 caughtSignal = true;
76}
77
78
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000079class FileTcpFilter : public TcpFilter
80{
81
82public:
83
84 FileTcpFilter(const char *fname)
85 : TcpFilter("-"), fileName(NULL), lastModTime(0)
86 {
87 if (fname != NULL)
88 fileName = strdup((char *)fname);
89 }
90
91 virtual ~FileTcpFilter()
92 {
93 if (fileName != NULL)
94 free(fileName);
95 }
96
97 virtual bool verifyConnection(Socket* s)
98 {
99 if (!reloadRules()) {
100 vlog.error("Could not read IP filtering rules: rejecting all clients");
101 filter.clear();
102 filter.push_back(parsePattern("-"));
103 return false;
104 }
105
106 return TcpFilter::verifyConnection(s);
107 }
108
109protected:
110
111 bool reloadRules()
112 {
113 if (fileName == NULL)
114 return true;
115
116 struct stat st;
117 if (stat(fileName, &st) != 0)
118 return false;
119
120 if (st.st_mtime != lastModTime) {
121 // Actually reload only if the file was modified
122 FILE *fp = fopen(fileName, "r");
123 if (fp == NULL)
124 return false;
125
126 // Remove all the rules from the parent class
127 filter.clear();
128
129 // Parse the file contents adding rules to the parent class
130 char buf[32];
131 while (readLine(buf, 32, fp)) {
132 if (buf[0] && strchr("+-?", buf[0])) {
133 filter.push_back(parsePattern(buf));
134 }
135 }
136
137 fclose(fp);
138 lastModTime = st.st_mtime;
139 }
140 return true;
141 }
142
143protected:
144
145 char *fileName;
146 time_t lastModTime;
147
148private:
149
150 //
151 // NOTE: we silently truncate long lines in this function.
152 //
153
154 bool readLine(char *buf, int bufSize, FILE *fp)
155 {
156 if (fp == NULL || buf == NULL || bufSize == 0)
157 return false;
158
159 if (fgets(buf, bufSize, fp) == NULL)
160 return false;
161
162 char *ptr = strchr(buf, '\n');
163 if (ptr != NULL) {
164 *ptr = '\0'; // remove newline at the end
165 } else {
166 if (!feof(fp)) {
167 int c;
168 do { // skip the rest of a long line
169 c = getc(fp);
170 } while (c != '\n' && c != EOF);
171 }
172 }
173 return true;
174 }
175
176};
177
178char* programName;
179
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000180static void printVersion(FILE *fp)
181{
Peter Åstrand4eacc022009-02-27 10:12:14 +0000182 fprintf(fp, "TigerVNC Server version %s, built %s\n",
Constantin Kaplinskyea7b6502008-09-28 05:08:48 +0000183 PACKAGE_VERSION, buildtime);
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000184}
185
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000186static void usage()
187{
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000188 printVersion(stderr);
189 fprintf(stderr, "\nUsage: %s [<parameters>]\n", programName);
190 fprintf(stderr, " %s --version\n", programName);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000191 fprintf(stderr,"\n"
192 "Parameters can be turned on with -<param> or off with -<param>=0\n"
193 "Parameters which take a value can be specified as "
194 "-<param> <value>\n"
195 "Other valid forms are <param>=<value> -<param>=<value> "
196 "--<param>=<value>\n"
197 "Parameter names are case-insensitive. The parameters are:\n\n");
198 Configuration::listParams(79, 14);
199 exit(1);
200}
201
202int main(int argc, char** argv)
203{
204 initStdIOLoggers();
205 LogWriter::setLogParams("*:stderr:30");
206
207 programName = argv[0];
208 Display* dpy;
209
Adam Tkacc58b3d12010-04-23 13:55:10 +0000210 Configuration::enableServerParams();
211
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000212 for (int i = 1; i < argc; i++) {
213 if (Configuration::setParam(argv[i]))
214 continue;
215
216 if (argv[i][0] == '-') {
217 if (i+1 < argc) {
218 if (Configuration::setParam(&argv[i][1], argv[i+1])) {
219 i++;
220 continue;
221 }
222 }
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000223 if (strcmp(argv[i], "-v") == 0 ||
224 strcmp(argv[i], "-version") == 0 ||
225 strcmp(argv[i], "--version") == 0) {
226 printVersion(stdout);
227 return 0;
228 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000229 usage();
230 }
231
232 usage();
233 }
234
235 CharArray dpyStr(displayname.getData());
236 if (!(dpy = XOpenDisplay(dpyStr.buf[0] ? dpyStr.buf : 0))) {
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000237 // FIXME: Why not vlog.error(...)?
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000238 fprintf(stderr,"%s: unable to open display \"%s\"\r\n",
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000239 programName, XDisplayName(dpyStr.buf));
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000240 exit(1);
241 }
242
243 signal(SIGHUP, CleanupSignalHandler);
244 signal(SIGINT, CleanupSignalHandler);
245 signal(SIGTERM, CleanupSignalHandler);
246
Pierre Ossmane3a2be62018-05-03 14:03:55 +0200247 std::list<SocketListener*> listeners;
Tim Waugh892d10a2015-03-11 13:12:07 +0000248
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000249 try {
250 TXWindow::init(dpy,"x0vncserver");
251 Geometry geo(DisplayWidth(dpy, DefaultScreen(dpy)),
252 DisplayHeight(dpy, DefaultScreen(dpy)));
Constantin Kaplinsky23c60222008-06-04 03:58:07 +0000253 if (geo.getRect().is_empty()) {
254 vlog.error("Exiting with error");
255 return 1;
256 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000257 XDesktop desktop(dpy, &geo);
Constantin Kaplinsky82328312008-04-24 08:44:24 +0000258
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000259 VNCServerST server("x0vncserver", &desktop);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000260
Pierre Ossman39594b82018-05-04 15:40:22 +0200261 if (rfbunixpath.getValueStr()[0] != '\0') {
262 listeners.push_back(new network::UnixListener(rfbunixpath, rfbunixmode));
263 vlog.info("Listening on %s (mode %04o)", (const char*)rfbunixpath, (int)rfbunixmode);
264 } else {
Pierre Ossmanab22bc02019-01-16 16:35:32 +0100265 if (localhostOnly)
266 createLocalTcpListeners(&listeners, (int)rfbport);
267 else
268 createTcpListeners(&listeners, 0, (int)rfbport);
Pierre Ossman39594b82018-05-04 15:40:22 +0200269 vlog.info("Listening on port %d", (int)rfbport);
270 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000271
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000272 const char *hostsData = hostsFile.getData();
273 FileTcpFilter fileTcpFilter(hostsData);
274 if (strlen(hostsData) != 0)
Pierre Ossmane3a2be62018-05-03 14:03:55 +0200275 for (std::list<SocketListener*>::iterator i = listeners.begin();
Tim Waugh892d10a2015-03-11 13:12:07 +0000276 i != listeners.end();
277 i++)
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200278 (*i)->setFilter(&fileTcpFilter);
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000279 delete[] hostsData;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000280
281 PollingScheduler sched((int)pollingCycle, (int)maxProcessorUsage);
282
283 while (!caughtSignal) {
Pierre Ossman278e4202016-04-29 14:28:54 +0200284 int wait_ms;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000285 struct timeval tv;
Pierre Ossman16419cc2016-04-29 14:29:43 +0200286 fd_set rfds, wfds;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000287 std::list<Socket*> sockets;
288 std::list<Socket*>::iterator i;
289
290 // Process any incoming X events
291 TXWindow::handleXEvents(dpy);
292
293 FD_ZERO(&rfds);
Pierre Ossman16419cc2016-04-29 14:29:43 +0200294 FD_ZERO(&wfds);
295
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000296 FD_SET(ConnectionNumber(dpy), &rfds);
Pierre Ossmane3a2be62018-05-03 14:03:55 +0200297 for (std::list<SocketListener*>::iterator i = listeners.begin();
Tim Waugh892d10a2015-03-11 13:12:07 +0000298 i != listeners.end();
299 i++)
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200300 FD_SET((*i)->getFd(), &rfds);
Tim Waugh892d10a2015-03-11 13:12:07 +0000301
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000302 server.getSockets(&sockets);
303 int clients_connected = 0;
304 for (i = sockets.begin(); i != sockets.end(); i++) {
305 if ((*i)->isShutdown()) {
306 server.removeSocket(*i);
307 delete (*i);
308 } else {
309 FD_SET((*i)->getFd(), &rfds);
Pierre Ossman16419cc2016-04-29 14:29:43 +0200310 if ((*i)->outStream().bufferUsage() > 0)
311 FD_SET((*i)->getFd(), &wfds);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000312 clients_connected++;
313 }
314 }
315
Constantin Kaplinsky813dbb42006-12-05 08:03:18 +0000316 if (!clients_connected)
317 sched.reset();
318
Pierre Ossman278e4202016-04-29 14:28:54 +0200319 wait_ms = 0;
320
Constantin Kaplinsky813dbb42006-12-05 08:03:18 +0000321 if (sched.isRunning()) {
Pierre Ossman278e4202016-04-29 14:28:54 +0200322 wait_ms = sched.millisRemaining();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000323 if (wait_ms > 500) {
324 wait_ms = 500;
325 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000326 }
Pierre Ossman278e4202016-04-29 14:28:54 +0200327
Pierre Ossmana4308c92018-10-26 15:54:56 +0200328 soonestTimeout(&wait_ms, Timer::checkTimeouts());
Pierre Ossman278e4202016-04-29 14:28:54 +0200329
330 tv.tv_sec = wait_ms / 1000;
331 tv.tv_usec = (wait_ms % 1000) * 1000;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000332
333 // Do the wait...
334 sched.sleepStarted();
Pierre Ossman16419cc2016-04-29 14:29:43 +0200335 int n = select(FD_SETSIZE, &rfds, &wfds, 0,
Pierre Ossman278e4202016-04-29 14:28:54 +0200336 wait_ms ? &tv : NULL);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000337 sched.sleepFinished();
338
339 if (n < 0) {
340 if (errno == EINTR) {
341 vlog.debug("Interrupted select() system call");
342 continue;
343 } else {
344 throw rdr::SystemException("select", errno);
345 }
346 }
347
348 // Accept new VNC connections
Pierre Ossmane3a2be62018-05-03 14:03:55 +0200349 for (std::list<SocketListener*>::iterator i = listeners.begin();
Tim Waugh892d10a2015-03-11 13:12:07 +0000350 i != listeners.end();
351 i++) {
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200352 if (FD_ISSET((*i)->getFd(), &rfds)) {
353 Socket* sock = (*i)->accept();
Tim Waugh892d10a2015-03-11 13:12:07 +0000354 if (sock) {
Pierre Ossman16419cc2016-04-29 14:29:43 +0200355 sock->outStream().setBlocking(false);
Tim Waugh892d10a2015-03-11 13:12:07 +0000356 server.addSocket(sock);
357 } else {
358 vlog.status("Client connection rejected");
359 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000360 }
361 }
362
Pierre Ossmana4308c92018-10-26 15:54:56 +0200363 Timer::checkTimeouts();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000364
365 // Client list could have been changed.
366 server.getSockets(&sockets);
367
368 // Nothing more to do if there are no client connections.
369 if (sockets.empty())
370 continue;
371
372 // Process events on existing VNC connections
373 for (i = sockets.begin(); i != sockets.end(); i++) {
374 if (FD_ISSET((*i)->getFd(), &rfds))
Pierre Ossmand408ca52016-04-29 14:26:05 +0200375 server.processSocketReadEvent(*i);
Pierre Ossman16419cc2016-04-29 14:29:43 +0200376 if (FD_ISSET((*i)->getFd(), &wfds))
377 server.processSocketWriteEvent(*i);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000378 }
379
380 if (desktop.isRunning() && sched.goodTimeToPoll()) {
381 sched.newPass();
382 desktop.poll();
383 }
384 }
385
386 } catch (rdr::Exception &e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000387 vlog.error("%s", e.str());
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000388 return 1;
389 }
390
Constantin Kaplinsky0c4306c2008-09-05 07:13:55 +0000391 TXWindow::handleXEvents(dpy);
392
Peter Åstrand (astrand)454d4442018-09-25 13:51:55 +0200393 // Run listener destructors; remove UNIX sockets etc
394 for (std::list<SocketListener*>::iterator i = listeners.begin();
395 i != listeners.end();
396 i++) {
397 delete *i;
398 }
399
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000400 vlog.info("Terminated");
401 return 0;
402}