blob: c8098f702baf94ca9782e3b98ecbed6cc7c279a3 [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>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000034
Constantin Kaplinskya3b60c42006-06-02 04:07:49 +000035#include <vncconfig/QueryConnectDialog.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000036
37#include <signal.h>
38#include <X11/X.h>
39#include <X11/Xlib.h>
40#include <X11/Xutil.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000041
Peter Åstrand (astrand)3112f502017-10-10 12:27:38 +020042#include <x0vncserver/XDesktop.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000043#include <x0vncserver/Geometry.h>
44#include <x0vncserver/Image.h>
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000045#include <x0vncserver/PollingScheduler.h>
46
Constantin Kaplinskyb9632702006-12-01 10:54:55 +000047extern char buildtime[];
48
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000049using namespace rfb;
50using namespace network;
51
52static LogWriter vlog("Main");
53
54IntParameter pollingCycle("PollingCycle", "Milliseconds per one polling "
55 "cycle; actual interval may be dynamically "
56 "adjusted to satisfy MaxProcessorUsage setting", 30);
57IntParameter maxProcessorUsage("MaxProcessorUsage", "Maximum percentage of "
58 "CPU time to be consumed", 35);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000059StringParameter displayname("display", "The X display", "");
60IntParameter rfbport("rfbport", "TCP port to listen for RFB protocol",5900);
61IntParameter queryConnectTimeout("QueryConnectTimeout",
62 "Number of seconds to show the Accept Connection dialog before "
63 "rejecting the connection",
64 10);
65StringParameter hostsFile("HostsFile", "File with IP access control rules", "");
66
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
79class QueryConnHandler : public VNCServerST::QueryConnectionHandler,
80 public QueryResultCallback {
81public:
82 QueryConnHandler(Display* dpy, VNCServerST* vs)
83 : display(dpy), server(vs), queryConnectDialog(0), queryConnectSock(0) {}
84 ~QueryConnHandler() { delete queryConnectDialog; }
85
86 // -=- VNCServerST::QueryConnectionHandler interface
87 virtual VNCServerST::queryResult queryConnection(network::Socket* sock,
88 const char* userName,
89 char** reason) {
90 if (queryConnectSock) {
Adam Tkacd36b6262009-09-04 10:57:20 +000091 *reason = strDup("Another connection is currently being queried.");
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000092 return VNCServerST::REJECT;
93 }
94 if (!userName) userName = "(anonymous)";
95 queryConnectSock = sock;
96 CharArray address(sock->getPeerAddress());
97 delete queryConnectDialog;
98 queryConnectDialog = new QueryConnectDialog(display, address.buf,
99 userName, queryConnectTimeout,
100 this);
101 queryConnectDialog->map();
102 return VNCServerST::PENDING;
103 }
104
105 // -=- QueryResultCallback interface
106 virtual void queryApproved() {
107 server->approveConnection(queryConnectSock, true, 0);
108 queryConnectSock = 0;
109 }
110 virtual void queryRejected() {
111 server->approveConnection(queryConnectSock, false,
112 "Connection rejected by local user");
113 queryConnectSock = 0;
114 }
115private:
116 Display* display;
117 VNCServerST* server;
118 QueryConnectDialog* queryConnectDialog;
119 network::Socket* queryConnectSock;
120};
121
122
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000123class FileTcpFilter : public TcpFilter
124{
125
126public:
127
128 FileTcpFilter(const char *fname)
129 : TcpFilter("-"), fileName(NULL), lastModTime(0)
130 {
131 if (fname != NULL)
132 fileName = strdup((char *)fname);
133 }
134
135 virtual ~FileTcpFilter()
136 {
137 if (fileName != NULL)
138 free(fileName);
139 }
140
141 virtual bool verifyConnection(Socket* s)
142 {
143 if (!reloadRules()) {
144 vlog.error("Could not read IP filtering rules: rejecting all clients");
145 filter.clear();
146 filter.push_back(parsePattern("-"));
147 return false;
148 }
149
150 return TcpFilter::verifyConnection(s);
151 }
152
153protected:
154
155 bool reloadRules()
156 {
157 if (fileName == NULL)
158 return true;
159
160 struct stat st;
161 if (stat(fileName, &st) != 0)
162 return false;
163
164 if (st.st_mtime != lastModTime) {
165 // Actually reload only if the file was modified
166 FILE *fp = fopen(fileName, "r");
167 if (fp == NULL)
168 return false;
169
170 // Remove all the rules from the parent class
171 filter.clear();
172
173 // Parse the file contents adding rules to the parent class
174 char buf[32];
175 while (readLine(buf, 32, fp)) {
176 if (buf[0] && strchr("+-?", buf[0])) {
177 filter.push_back(parsePattern(buf));
178 }
179 }
180
181 fclose(fp);
182 lastModTime = st.st_mtime;
183 }
184 return true;
185 }
186
187protected:
188
189 char *fileName;
190 time_t lastModTime;
191
192private:
193
194 //
195 // NOTE: we silently truncate long lines in this function.
196 //
197
198 bool readLine(char *buf, int bufSize, FILE *fp)
199 {
200 if (fp == NULL || buf == NULL || bufSize == 0)
201 return false;
202
203 if (fgets(buf, bufSize, fp) == NULL)
204 return false;
205
206 char *ptr = strchr(buf, '\n');
207 if (ptr != NULL) {
208 *ptr = '\0'; // remove newline at the end
209 } else {
210 if (!feof(fp)) {
211 int c;
212 do { // skip the rest of a long line
213 c = getc(fp);
214 } while (c != '\n' && c != EOF);
215 }
216 }
217 return true;
218 }
219
220};
221
222char* programName;
223
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000224static void printVersion(FILE *fp)
225{
Peter Åstrand4eacc022009-02-27 10:12:14 +0000226 fprintf(fp, "TigerVNC Server version %s, built %s\n",
Constantin Kaplinskyea7b6502008-09-28 05:08:48 +0000227 PACKAGE_VERSION, buildtime);
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000228}
229
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000230static void usage()
231{
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000232 printVersion(stderr);
233 fprintf(stderr, "\nUsage: %s [<parameters>]\n", programName);
234 fprintf(stderr, " %s --version\n", programName);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000235 fprintf(stderr,"\n"
236 "Parameters can be turned on with -<param> or off with -<param>=0\n"
237 "Parameters which take a value can be specified as "
238 "-<param> <value>\n"
239 "Other valid forms are <param>=<value> -<param>=<value> "
240 "--<param>=<value>\n"
241 "Parameter names are case-insensitive. The parameters are:\n\n");
242 Configuration::listParams(79, 14);
243 exit(1);
244}
245
246int main(int argc, char** argv)
247{
248 initStdIOLoggers();
249 LogWriter::setLogParams("*:stderr:30");
250
251 programName = argv[0];
252 Display* dpy;
253
Adam Tkacc58b3d12010-04-23 13:55:10 +0000254 Configuration::enableServerParams();
255
Peter Åstrand (astrand)0a0e5822017-10-18 08:54:05 +0200256 // Disable configuration parameters which we do not support
257 Configuration::removeParam("AcceptSetDesktopSize");
258
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000259 for (int i = 1; i < argc; i++) {
260 if (Configuration::setParam(argv[i]))
261 continue;
262
263 if (argv[i][0] == '-') {
264 if (i+1 < argc) {
265 if (Configuration::setParam(&argv[i][1], argv[i+1])) {
266 i++;
267 continue;
268 }
269 }
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000270 if (strcmp(argv[i], "-v") == 0 ||
271 strcmp(argv[i], "-version") == 0 ||
272 strcmp(argv[i], "--version") == 0) {
273 printVersion(stdout);
274 return 0;
275 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000276 usage();
277 }
278
279 usage();
280 }
281
282 CharArray dpyStr(displayname.getData());
283 if (!(dpy = XOpenDisplay(dpyStr.buf[0] ? dpyStr.buf : 0))) {
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000284 // FIXME: Why not vlog.error(...)?
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000285 fprintf(stderr,"%s: unable to open display \"%s\"\r\n",
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000286 programName, XDisplayName(dpyStr.buf));
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000287 exit(1);
288 }
289
290 signal(SIGHUP, CleanupSignalHandler);
291 signal(SIGINT, CleanupSignalHandler);
292 signal(SIGTERM, CleanupSignalHandler);
293
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200294 std::list<TcpListener*> listeners;
Tim Waugh892d10a2015-03-11 13:12:07 +0000295
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000296 try {
297 TXWindow::init(dpy,"x0vncserver");
298 Geometry geo(DisplayWidth(dpy, DefaultScreen(dpy)),
299 DisplayHeight(dpy, DefaultScreen(dpy)));
Constantin Kaplinsky23c60222008-06-04 03:58:07 +0000300 if (geo.getRect().is_empty()) {
301 vlog.error("Exiting with error");
302 return 1;
303 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000304 XDesktop desktop(dpy, &geo);
Constantin Kaplinsky82328312008-04-24 08:44:24 +0000305
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000306 VNCServerST server("x0vncserver", &desktop);
307 QueryConnHandler qcHandler(dpy, &server);
308 server.setQueryConnectionHandler(&qcHandler);
309
Tim Waugh892d10a2015-03-11 13:12:07 +0000310 createTcpListeners(&listeners, 0, (int)rfbport);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000311 vlog.info("Listening on port %d", (int)rfbport);
312
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000313 const char *hostsData = hostsFile.getData();
314 FileTcpFilter fileTcpFilter(hostsData);
315 if (strlen(hostsData) != 0)
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200316 for (std::list<TcpListener*>::iterator i = listeners.begin();
Tim Waugh892d10a2015-03-11 13:12:07 +0000317 i != listeners.end();
318 i++)
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200319 (*i)->setFilter(&fileTcpFilter);
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000320 delete[] hostsData;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000321
322 PollingScheduler sched((int)pollingCycle, (int)maxProcessorUsage);
323
324 while (!caughtSignal) {
Pierre Ossman278e4202016-04-29 14:28:54 +0200325 int wait_ms;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000326 struct timeval tv;
Pierre Ossman16419cc2016-04-29 14:29:43 +0200327 fd_set rfds, wfds;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000328 std::list<Socket*> sockets;
329 std::list<Socket*>::iterator i;
330
331 // Process any incoming X events
332 TXWindow::handleXEvents(dpy);
333
334 FD_ZERO(&rfds);
Pierre Ossman16419cc2016-04-29 14:29:43 +0200335 FD_ZERO(&wfds);
336
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000337 FD_SET(ConnectionNumber(dpy), &rfds);
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200338 for (std::list<TcpListener*>::iterator i = listeners.begin();
Tim Waugh892d10a2015-03-11 13:12:07 +0000339 i != listeners.end();
340 i++)
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200341 FD_SET((*i)->getFd(), &rfds);
Tim Waugh892d10a2015-03-11 13:12:07 +0000342
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000343 server.getSockets(&sockets);
344 int clients_connected = 0;
345 for (i = sockets.begin(); i != sockets.end(); i++) {
346 if ((*i)->isShutdown()) {
347 server.removeSocket(*i);
348 delete (*i);
349 } else {
350 FD_SET((*i)->getFd(), &rfds);
Pierre Ossman16419cc2016-04-29 14:29:43 +0200351 if ((*i)->outStream().bufferUsage() > 0)
352 FD_SET((*i)->getFd(), &wfds);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000353 clients_connected++;
354 }
355 }
356
Constantin Kaplinsky813dbb42006-12-05 08:03:18 +0000357 if (!clients_connected)
358 sched.reset();
359
Pierre Ossman278e4202016-04-29 14:28:54 +0200360 wait_ms = 0;
361
Constantin Kaplinsky813dbb42006-12-05 08:03:18 +0000362 if (sched.isRunning()) {
Pierre Ossman278e4202016-04-29 14:28:54 +0200363 wait_ms = sched.millisRemaining();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000364 if (wait_ms > 500) {
365 wait_ms = 500;
366 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000367 }
Pierre Ossman278e4202016-04-29 14:28:54 +0200368
369 soonestTimeout(&wait_ms, server.checkTimeouts());
370
371 tv.tv_sec = wait_ms / 1000;
372 tv.tv_usec = (wait_ms % 1000) * 1000;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000373
374 // Do the wait...
375 sched.sleepStarted();
Pierre Ossman16419cc2016-04-29 14:29:43 +0200376 int n = select(FD_SETSIZE, &rfds, &wfds, 0,
Pierre Ossman278e4202016-04-29 14:28:54 +0200377 wait_ms ? &tv : NULL);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000378 sched.sleepFinished();
379
380 if (n < 0) {
381 if (errno == EINTR) {
382 vlog.debug("Interrupted select() system call");
383 continue;
384 } else {
385 throw rdr::SystemException("select", errno);
386 }
387 }
388
389 // Accept new VNC connections
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200390 for (std::list<TcpListener*>::iterator i = listeners.begin();
Tim Waugh892d10a2015-03-11 13:12:07 +0000391 i != listeners.end();
392 i++) {
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200393 if (FD_ISSET((*i)->getFd(), &rfds)) {
394 Socket* sock = (*i)->accept();
Tim Waugh892d10a2015-03-11 13:12:07 +0000395 if (sock) {
Pierre Ossman16419cc2016-04-29 14:29:43 +0200396 sock->outStream().setBlocking(false);
Tim Waugh892d10a2015-03-11 13:12:07 +0000397 server.addSocket(sock);
398 } else {
399 vlog.status("Client connection rejected");
400 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000401 }
402 }
403
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000404 server.checkTimeouts();
405
406 // Client list could have been changed.
407 server.getSockets(&sockets);
408
409 // Nothing more to do if there are no client connections.
410 if (sockets.empty())
411 continue;
412
413 // Process events on existing VNC connections
414 for (i = sockets.begin(); i != sockets.end(); i++) {
415 if (FD_ISSET((*i)->getFd(), &rfds))
Pierre Ossmand408ca52016-04-29 14:26:05 +0200416 server.processSocketReadEvent(*i);
Pierre Ossman16419cc2016-04-29 14:29:43 +0200417 if (FD_ISSET((*i)->getFd(), &wfds))
418 server.processSocketWriteEvent(*i);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000419 }
420
421 if (desktop.isRunning() && sched.goodTimeToPoll()) {
422 sched.newPass();
423 desktop.poll();
424 }
425 }
426
427 } catch (rdr::Exception &e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000428 vlog.error("%s", e.str());
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000429 return 1;
430 }
431
Constantin Kaplinsky0c4306c2008-09-05 07:13:55 +0000432 TXWindow::handleXEvents(dpy);
433
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000434 vlog.info("Terminated");
435 return 0;
436}