blob: d0d4be8645e8541e32f10586389d391363133c17 [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 Kaplinskya3b60c42006-06-02 04:07:49 +000036#include <vncconfig/QueryConnectDialog.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000037
38#include <signal.h>
39#include <X11/X.h>
40#include <X11/Xlib.h>
41#include <X11/Xutil.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000042
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020043#include <x0vncserver/XDesktop.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000044#include <x0vncserver/Geometry.h>
45#include <x0vncserver/Image.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000046#include <x0vncserver/PollingScheduler.h>
47
Constantin Kaplinskyb9632702006-12-01 10:54:55 +000048extern char buildtime[];
49
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000050using namespace rfb;
51using namespace network;
52
53static LogWriter vlog("Main");
54
55IntParameter pollingCycle("PollingCycle", "Milliseconds per one polling "
56 "cycle; actual interval may be dynamically "
57 "adjusted to satisfy MaxProcessorUsage setting", 30);
58IntParameter maxProcessorUsage("MaxProcessorUsage", "Maximum percentage of "
59 "CPU time to be consumed", 35);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000060StringParameter displayname("display", "The X display", "");
61IntParameter rfbport("rfbport", "TCP port to listen for RFB protocol",5900);
Pierre Ossman39594b82018-05-04 15:40:22 +020062StringParameter rfbunixpath("rfbunixpath", "Unix socket to listen for RFB protocol", "");
63IntParameter rfbunixmode("rfbunixmode", "Unix socket access mode", 0600);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000064IntParameter queryConnectTimeout("QueryConnectTimeout",
65 "Number of seconds to show the Accept Connection dialog before "
66 "rejecting the connection",
67 10);
68StringParameter hostsFile("HostsFile", "File with IP access control rules", "");
69
70//
71// Allow the main loop terminate itself gracefully on receiving a signal.
72//
73
74static bool caughtSignal = false;
75
76static void CleanupSignalHandler(int sig)
77{
78 caughtSignal = true;
79}
80
81
82class QueryConnHandler : public VNCServerST::QueryConnectionHandler,
83 public QueryResultCallback {
84public:
85 QueryConnHandler(Display* dpy, VNCServerST* vs)
86 : display(dpy), server(vs), queryConnectDialog(0), queryConnectSock(0) {}
87 ~QueryConnHandler() { delete queryConnectDialog; }
88
89 // -=- VNCServerST::QueryConnectionHandler interface
90 virtual VNCServerST::queryResult queryConnection(network::Socket* sock,
91 const char* userName,
92 char** reason) {
93 if (queryConnectSock) {
Adam Tkacd36b6262009-09-04 10:57:20 +000094 *reason = strDup("Another connection is currently being queried.");
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000095 return VNCServerST::REJECT;
96 }
97 if (!userName) userName = "(anonymous)";
98 queryConnectSock = sock;
99 CharArray address(sock->getPeerAddress());
100 delete queryConnectDialog;
101 queryConnectDialog = new QueryConnectDialog(display, address.buf,
102 userName, queryConnectTimeout,
103 this);
104 queryConnectDialog->map();
105 return VNCServerST::PENDING;
106 }
107
108 // -=- QueryResultCallback interface
109 virtual void queryApproved() {
110 server->approveConnection(queryConnectSock, true, 0);
111 queryConnectSock = 0;
112 }
113 virtual void queryRejected() {
114 server->approveConnection(queryConnectSock, false,
115 "Connection rejected by local user");
116 queryConnectSock = 0;
117 }
118private:
119 Display* display;
120 VNCServerST* server;
121 QueryConnectDialog* queryConnectDialog;
122 network::Socket* queryConnectSock;
123};
124
125
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000126class FileTcpFilter : public TcpFilter
127{
128
129public:
130
131 FileTcpFilter(const char *fname)
132 : TcpFilter("-"), fileName(NULL), lastModTime(0)
133 {
134 if (fname != NULL)
135 fileName = strdup((char *)fname);
136 }
137
138 virtual ~FileTcpFilter()
139 {
140 if (fileName != NULL)
141 free(fileName);
142 }
143
144 virtual bool verifyConnection(Socket* s)
145 {
146 if (!reloadRules()) {
147 vlog.error("Could not read IP filtering rules: rejecting all clients");
148 filter.clear();
149 filter.push_back(parsePattern("-"));
150 return false;
151 }
152
153 return TcpFilter::verifyConnection(s);
154 }
155
156protected:
157
158 bool reloadRules()
159 {
160 if (fileName == NULL)
161 return true;
162
163 struct stat st;
164 if (stat(fileName, &st) != 0)
165 return false;
166
167 if (st.st_mtime != lastModTime) {
168 // Actually reload only if the file was modified
169 FILE *fp = fopen(fileName, "r");
170 if (fp == NULL)
171 return false;
172
173 // Remove all the rules from the parent class
174 filter.clear();
175
176 // Parse the file contents adding rules to the parent class
177 char buf[32];
178 while (readLine(buf, 32, fp)) {
179 if (buf[0] && strchr("+-?", buf[0])) {
180 filter.push_back(parsePattern(buf));
181 }
182 }
183
184 fclose(fp);
185 lastModTime = st.st_mtime;
186 }
187 return true;
188 }
189
190protected:
191
192 char *fileName;
193 time_t lastModTime;
194
195private:
196
197 //
198 // NOTE: we silently truncate long lines in this function.
199 //
200
201 bool readLine(char *buf, int bufSize, FILE *fp)
202 {
203 if (fp == NULL || buf == NULL || bufSize == 0)
204 return false;
205
206 if (fgets(buf, bufSize, fp) == NULL)
207 return false;
208
209 char *ptr = strchr(buf, '\n');
210 if (ptr != NULL) {
211 *ptr = '\0'; // remove newline at the end
212 } else {
213 if (!feof(fp)) {
214 int c;
215 do { // skip the rest of a long line
216 c = getc(fp);
217 } while (c != '\n' && c != EOF);
218 }
219 }
220 return true;
221 }
222
223};
224
225char* programName;
226
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000227static void printVersion(FILE *fp)
228{
Peter Åstrand4eacc022009-02-27 10:12:14 +0000229 fprintf(fp, "TigerVNC Server version %s, built %s\n",
Constantin Kaplinskyea7b6502008-09-28 05:08:48 +0000230 PACKAGE_VERSION, buildtime);
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000231}
232
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000233static void usage()
234{
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000235 printVersion(stderr);
236 fprintf(stderr, "\nUsage: %s [<parameters>]\n", programName);
237 fprintf(stderr, " %s --version\n", programName);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000238 fprintf(stderr,"\n"
239 "Parameters can be turned on with -<param> or off with -<param>=0\n"
240 "Parameters which take a value can be specified as "
241 "-<param> <value>\n"
242 "Other valid forms are <param>=<value> -<param>=<value> "
243 "--<param>=<value>\n"
244 "Parameter names are case-insensitive. The parameters are:\n\n");
245 Configuration::listParams(79, 14);
246 exit(1);
247}
248
249int main(int argc, char** argv)
250{
251 initStdIOLoggers();
252 LogWriter::setLogParams("*:stderr:30");
253
254 programName = argv[0];
255 Display* dpy;
256
Adam Tkacc58b3d12010-04-23 13:55:10 +0000257 Configuration::enableServerParams();
258
Peter Åstrand (astrand)0a0e5822017-10-18 08:54:05 +0200259 // Disable configuration parameters which we do not support
260 Configuration::removeParam("AcceptSetDesktopSize");
261
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000262 for (int i = 1; i < argc; i++) {
263 if (Configuration::setParam(argv[i]))
264 continue;
265
266 if (argv[i][0] == '-') {
267 if (i+1 < argc) {
268 if (Configuration::setParam(&argv[i][1], argv[i+1])) {
269 i++;
270 continue;
271 }
272 }
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000273 if (strcmp(argv[i], "-v") == 0 ||
274 strcmp(argv[i], "-version") == 0 ||
275 strcmp(argv[i], "--version") == 0) {
276 printVersion(stdout);
277 return 0;
278 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000279 usage();
280 }
281
282 usage();
283 }
284
285 CharArray dpyStr(displayname.getData());
286 if (!(dpy = XOpenDisplay(dpyStr.buf[0] ? dpyStr.buf : 0))) {
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000287 // FIXME: Why not vlog.error(...)?
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000288 fprintf(stderr,"%s: unable to open display \"%s\"\r\n",
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000289 programName, XDisplayName(dpyStr.buf));
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000290 exit(1);
291 }
292
293 signal(SIGHUP, CleanupSignalHandler);
294 signal(SIGINT, CleanupSignalHandler);
295 signal(SIGTERM, CleanupSignalHandler);
296
Pierre Ossmane3a2be62018-05-03 14:03:55 +0200297 std::list<SocketListener*> listeners;
Tim Waugh892d10a2015-03-11 13:12:07 +0000298
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000299 try {
300 TXWindow::init(dpy,"x0vncserver");
301 Geometry geo(DisplayWidth(dpy, DefaultScreen(dpy)),
302 DisplayHeight(dpy, DefaultScreen(dpy)));
Constantin Kaplinsky23c60222008-06-04 03:58:07 +0000303 if (geo.getRect().is_empty()) {
304 vlog.error("Exiting with error");
305 return 1;
306 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000307 XDesktop desktop(dpy, &geo);
Constantin Kaplinsky82328312008-04-24 08:44:24 +0000308
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000309 VNCServerST server("x0vncserver", &desktop);
310 QueryConnHandler qcHandler(dpy, &server);
311 server.setQueryConnectionHandler(&qcHandler);
312
Pierre Ossman39594b82018-05-04 15:40:22 +0200313 if (rfbunixpath.getValueStr()[0] != '\0') {
314 listeners.push_back(new network::UnixListener(rfbunixpath, rfbunixmode));
315 vlog.info("Listening on %s (mode %04o)", (const char*)rfbunixpath, (int)rfbunixmode);
316 } else {
317 createTcpListeners(&listeners, 0, (int)rfbport);
318 vlog.info("Listening on port %d", (int)rfbport);
319 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000320
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000321 const char *hostsData = hostsFile.getData();
322 FileTcpFilter fileTcpFilter(hostsData);
323 if (strlen(hostsData) != 0)
Pierre Ossmane3a2be62018-05-03 14:03:55 +0200324 for (std::list<SocketListener*>::iterator i = listeners.begin();
Tim Waugh892d10a2015-03-11 13:12:07 +0000325 i != listeners.end();
326 i++)
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200327 (*i)->setFilter(&fileTcpFilter);
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000328 delete[] hostsData;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000329
330 PollingScheduler sched((int)pollingCycle, (int)maxProcessorUsage);
331
332 while (!caughtSignal) {
Pierre Ossman278e4202016-04-29 14:28:54 +0200333 int wait_ms;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000334 struct timeval tv;
Pierre Ossman16419cc2016-04-29 14:29:43 +0200335 fd_set rfds, wfds;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000336 std::list<Socket*> sockets;
337 std::list<Socket*>::iterator i;
338
339 // Process any incoming X events
340 TXWindow::handleXEvents(dpy);
341
342 FD_ZERO(&rfds);
Pierre Ossman16419cc2016-04-29 14:29:43 +0200343 FD_ZERO(&wfds);
344
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000345 FD_SET(ConnectionNumber(dpy), &rfds);
Pierre Ossmane3a2be62018-05-03 14:03:55 +0200346 for (std::list<SocketListener*>::iterator i = listeners.begin();
Tim Waugh892d10a2015-03-11 13:12:07 +0000347 i != listeners.end();
348 i++)
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200349 FD_SET((*i)->getFd(), &rfds);
Tim Waugh892d10a2015-03-11 13:12:07 +0000350
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000351 server.getSockets(&sockets);
352 int clients_connected = 0;
353 for (i = sockets.begin(); i != sockets.end(); i++) {
354 if ((*i)->isShutdown()) {
355 server.removeSocket(*i);
356 delete (*i);
357 } else {
358 FD_SET((*i)->getFd(), &rfds);
Pierre Ossman16419cc2016-04-29 14:29:43 +0200359 if ((*i)->outStream().bufferUsage() > 0)
360 FD_SET((*i)->getFd(), &wfds);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000361 clients_connected++;
362 }
363 }
364
Constantin Kaplinsky813dbb42006-12-05 08:03:18 +0000365 if (!clients_connected)
366 sched.reset();
367
Pierre Ossman278e4202016-04-29 14:28:54 +0200368 wait_ms = 0;
369
Constantin Kaplinsky813dbb42006-12-05 08:03:18 +0000370 if (sched.isRunning()) {
Pierre Ossman278e4202016-04-29 14:28:54 +0200371 wait_ms = sched.millisRemaining();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000372 if (wait_ms > 500) {
373 wait_ms = 500;
374 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000375 }
Pierre Ossman278e4202016-04-29 14:28:54 +0200376
377 soonestTimeout(&wait_ms, server.checkTimeouts());
378
379 tv.tv_sec = wait_ms / 1000;
380 tv.tv_usec = (wait_ms % 1000) * 1000;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000381
382 // Do the wait...
383 sched.sleepStarted();
Pierre Ossman16419cc2016-04-29 14:29:43 +0200384 int n = select(FD_SETSIZE, &rfds, &wfds, 0,
Pierre Ossman278e4202016-04-29 14:28:54 +0200385 wait_ms ? &tv : NULL);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000386 sched.sleepFinished();
387
388 if (n < 0) {
389 if (errno == EINTR) {
390 vlog.debug("Interrupted select() system call");
391 continue;
392 } else {
393 throw rdr::SystemException("select", errno);
394 }
395 }
396
397 // Accept new VNC connections
Pierre Ossmane3a2be62018-05-03 14:03:55 +0200398 for (std::list<SocketListener*>::iterator i = listeners.begin();
Tim Waugh892d10a2015-03-11 13:12:07 +0000399 i != listeners.end();
400 i++) {
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200401 if (FD_ISSET((*i)->getFd(), &rfds)) {
402 Socket* sock = (*i)->accept();
Tim Waugh892d10a2015-03-11 13:12:07 +0000403 if (sock) {
Pierre Ossman16419cc2016-04-29 14:29:43 +0200404 sock->outStream().setBlocking(false);
Tim Waugh892d10a2015-03-11 13:12:07 +0000405 server.addSocket(sock);
406 } else {
407 vlog.status("Client connection rejected");
408 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000409 }
410 }
411
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000412 server.checkTimeouts();
413
414 // Client list could have been changed.
415 server.getSockets(&sockets);
416
417 // Nothing more to do if there are no client connections.
418 if (sockets.empty())
419 continue;
420
421 // Process events on existing VNC connections
422 for (i = sockets.begin(); i != sockets.end(); i++) {
423 if (FD_ISSET((*i)->getFd(), &rfds))
Pierre Ossmand408ca52016-04-29 14:26:05 +0200424 server.processSocketReadEvent(*i);
Pierre Ossman16419cc2016-04-29 14:29:43 +0200425 if (FD_ISSET((*i)->getFd(), &wfds))
426 server.processSocketWriteEvent(*i);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000427 }
428
429 if (desktop.isRunning() && sched.goodTimeToPoll()) {
430 sched.newPass();
431 desktop.poll();
432 }
433 }
434
435 } catch (rdr::Exception &e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000436 vlog.error("%s", e.str());
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000437 return 1;
438 }
439
Constantin Kaplinsky0c4306c2008-09-05 07:13:55 +0000440 TXWindow::handleXEvents(dpy);
441
Peter Åstrand (astrand)454d4442018-09-25 13:51:55 +0200442 // Run listener destructors; remove UNIX sockets etc
443 for (std::list<SocketListener*>::iterator i = listeners.begin();
444 i != listeners.end();
445 i++) {
446 delete *i;
447 }
448
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000449 vlog.info("Terminated");
450 return 0;
451}