blob: b21daae468d2f9b37b39f5cb402e275a0b7b6064 [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
47// XXX Lynx/OS 2.3: protos for select(), bzero()
48#ifdef Lynx
49#include <sys/proto.h>
50#endif
51
Constantin Kaplinskyb9632702006-12-01 10:54:55 +000052extern char buildtime[];
53
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000054using namespace rfb;
55using namespace network;
56
57static LogWriter vlog("Main");
58
59IntParameter pollingCycle("PollingCycle", "Milliseconds per one polling "
60 "cycle; actual interval may be dynamically "
61 "adjusted to satisfy MaxProcessorUsage setting", 30);
62IntParameter maxProcessorUsage("MaxProcessorUsage", "Maximum percentage of "
63 "CPU time to be consumed", 35);
64BoolParameter useShm("UseSHM", "Use MIT-SHM extension if available", true);
65BoolParameter useOverlay("OverlayMode", "Use overlay mode under "
66 "IRIX or Solaris", true);
67StringParameter displayname("display", "The X display", "");
68IntParameter rfbport("rfbport", "TCP port to listen for RFB protocol",5900);
69IntParameter queryConnectTimeout("QueryConnectTimeout",
70 "Number of seconds to show the Accept Connection dialog before "
71 "rejecting the connection",
72 10);
73StringParameter hostsFile("HostsFile", "File with IP access control rules", "");
Peter Korsgaard8fe50902017-07-13 00:35:57 +020074BoolParameter rawKeyboard("RawKeyboard",
75 "Send keyboard events straight through and "
76 "avoid mapping them to the current keyboard "
77 "layout", false);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000078
79//
80// Allow the main loop terminate itself gracefully on receiving a signal.
81//
82
83static bool caughtSignal = false;
84
85static void CleanupSignalHandler(int sig)
86{
87 caughtSignal = true;
88}
89
90
91class QueryConnHandler : public VNCServerST::QueryConnectionHandler,
92 public QueryResultCallback {
93public:
94 QueryConnHandler(Display* dpy, VNCServerST* vs)
95 : display(dpy), server(vs), queryConnectDialog(0), queryConnectSock(0) {}
96 ~QueryConnHandler() { delete queryConnectDialog; }
97
98 // -=- VNCServerST::QueryConnectionHandler interface
99 virtual VNCServerST::queryResult queryConnection(network::Socket* sock,
100 const char* userName,
101 char** reason) {
102 if (queryConnectSock) {
Adam Tkacd36b6262009-09-04 10:57:20 +0000103 *reason = strDup("Another connection is currently being queried.");
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000104 return VNCServerST::REJECT;
105 }
106 if (!userName) userName = "(anonymous)";
107 queryConnectSock = sock;
108 CharArray address(sock->getPeerAddress());
109 delete queryConnectDialog;
110 queryConnectDialog = new QueryConnectDialog(display, address.buf,
111 userName, queryConnectTimeout,
112 this);
113 queryConnectDialog->map();
114 return VNCServerST::PENDING;
115 }
116
117 // -=- QueryResultCallback interface
118 virtual void queryApproved() {
119 server->approveConnection(queryConnectSock, true, 0);
120 queryConnectSock = 0;
121 }
122 virtual void queryRejected() {
123 server->approveConnection(queryConnectSock, false,
124 "Connection rejected by local user");
125 queryConnectSock = 0;
126 }
127private:
128 Display* display;
129 VNCServerST* server;
130 QueryConnectDialog* queryConnectDialog;
131 network::Socket* queryConnectSock;
132};
133
134
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000135class FileTcpFilter : public TcpFilter
136{
137
138public:
139
140 FileTcpFilter(const char *fname)
141 : TcpFilter("-"), fileName(NULL), lastModTime(0)
142 {
143 if (fname != NULL)
144 fileName = strdup((char *)fname);
145 }
146
147 virtual ~FileTcpFilter()
148 {
149 if (fileName != NULL)
150 free(fileName);
151 }
152
153 virtual bool verifyConnection(Socket* s)
154 {
155 if (!reloadRules()) {
156 vlog.error("Could not read IP filtering rules: rejecting all clients");
157 filter.clear();
158 filter.push_back(parsePattern("-"));
159 return false;
160 }
161
162 return TcpFilter::verifyConnection(s);
163 }
164
165protected:
166
167 bool reloadRules()
168 {
169 if (fileName == NULL)
170 return true;
171
172 struct stat st;
173 if (stat(fileName, &st) != 0)
174 return false;
175
176 if (st.st_mtime != lastModTime) {
177 // Actually reload only if the file was modified
178 FILE *fp = fopen(fileName, "r");
179 if (fp == NULL)
180 return false;
181
182 // Remove all the rules from the parent class
183 filter.clear();
184
185 // Parse the file contents adding rules to the parent class
186 char buf[32];
187 while (readLine(buf, 32, fp)) {
188 if (buf[0] && strchr("+-?", buf[0])) {
189 filter.push_back(parsePattern(buf));
190 }
191 }
192
193 fclose(fp);
194 lastModTime = st.st_mtime;
195 }
196 return true;
197 }
198
199protected:
200
201 char *fileName;
202 time_t lastModTime;
203
204private:
205
206 //
207 // NOTE: we silently truncate long lines in this function.
208 //
209
210 bool readLine(char *buf, int bufSize, FILE *fp)
211 {
212 if (fp == NULL || buf == NULL || bufSize == 0)
213 return false;
214
215 if (fgets(buf, bufSize, fp) == NULL)
216 return false;
217
218 char *ptr = strchr(buf, '\n');
219 if (ptr != NULL) {
220 *ptr = '\0'; // remove newline at the end
221 } else {
222 if (!feof(fp)) {
223 int c;
224 do { // skip the rest of a long line
225 c = getc(fp);
226 } while (c != '\n' && c != EOF);
227 }
228 }
229 return true;
230 }
231
232};
233
234char* programName;
235
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000236static void printVersion(FILE *fp)
237{
Peter Åstrand4eacc022009-02-27 10:12:14 +0000238 fprintf(fp, "TigerVNC Server version %s, built %s\n",
Constantin Kaplinskyea7b6502008-09-28 05:08:48 +0000239 PACKAGE_VERSION, buildtime);
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000240}
241
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000242static void usage()
243{
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000244 printVersion(stderr);
245 fprintf(stderr, "\nUsage: %s [<parameters>]\n", programName);
246 fprintf(stderr, " %s --version\n", programName);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000247 fprintf(stderr,"\n"
248 "Parameters can be turned on with -<param> or off with -<param>=0\n"
249 "Parameters which take a value can be specified as "
250 "-<param> <value>\n"
251 "Other valid forms are <param>=<value> -<param>=<value> "
252 "--<param>=<value>\n"
253 "Parameter names are case-insensitive. The parameters are:\n\n");
254 Configuration::listParams(79, 14);
255 exit(1);
256}
257
258int main(int argc, char** argv)
259{
260 initStdIOLoggers();
261 LogWriter::setLogParams("*:stderr:30");
262
263 programName = argv[0];
264 Display* dpy;
265
Adam Tkacc58b3d12010-04-23 13:55:10 +0000266 Configuration::enableServerParams();
267
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000268 for (int i = 1; i < argc; i++) {
269 if (Configuration::setParam(argv[i]))
270 continue;
271
272 if (argv[i][0] == '-') {
273 if (i+1 < argc) {
274 if (Configuration::setParam(&argv[i][1], argv[i+1])) {
275 i++;
276 continue;
277 }
278 }
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000279 if (strcmp(argv[i], "-v") == 0 ||
280 strcmp(argv[i], "-version") == 0 ||
281 strcmp(argv[i], "--version") == 0) {
282 printVersion(stdout);
283 return 0;
284 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000285 usage();
286 }
287
288 usage();
289 }
290
291 CharArray dpyStr(displayname.getData());
292 if (!(dpy = XOpenDisplay(dpyStr.buf[0] ? dpyStr.buf : 0))) {
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000293 // FIXME: Why not vlog.error(...)?
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000294 fprintf(stderr,"%s: unable to open display \"%s\"\r\n",
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000295 programName, XDisplayName(dpyStr.buf));
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000296 exit(1);
297 }
298
299 signal(SIGHUP, CleanupSignalHandler);
300 signal(SIGINT, CleanupSignalHandler);
301 signal(SIGTERM, CleanupSignalHandler);
302
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200303 std::list<TcpListener*> listeners;
Tim Waugh892d10a2015-03-11 13:12:07 +0000304
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000305 try {
306 TXWindow::init(dpy,"x0vncserver");
307 Geometry geo(DisplayWidth(dpy, DefaultScreen(dpy)),
308 DisplayHeight(dpy, DefaultScreen(dpy)));
Constantin Kaplinsky23c60222008-06-04 03:58:07 +0000309 if (geo.getRect().is_empty()) {
310 vlog.error("Exiting with error");
311 return 1;
312 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000313 XDesktop desktop(dpy, &geo);
Constantin Kaplinsky82328312008-04-24 08:44:24 +0000314
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000315 VNCServerST server("x0vncserver", &desktop);
316 QueryConnHandler qcHandler(dpy, &server);
317 server.setQueryConnectionHandler(&qcHandler);
318
Tim Waugh892d10a2015-03-11 13:12:07 +0000319 createTcpListeners(&listeners, 0, (int)rfbport);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000320 vlog.info("Listening on port %d", (int)rfbport);
321
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000322 const char *hostsData = hostsFile.getData();
323 FileTcpFilter fileTcpFilter(hostsData);
324 if (strlen(hostsData) != 0)
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200325 for (std::list<TcpListener*>::iterator i = listeners.begin();
Tim Waugh892d10a2015-03-11 13:12:07 +0000326 i != listeners.end();
327 i++)
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200328 (*i)->setFilter(&fileTcpFilter);
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000329 delete[] hostsData;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000330
331 PollingScheduler sched((int)pollingCycle, (int)maxProcessorUsage);
332
333 while (!caughtSignal) {
Pierre Ossman278e4202016-04-29 14:28:54 +0200334 int wait_ms;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000335 struct timeval tv;
Pierre Ossman16419cc2016-04-29 14:29:43 +0200336 fd_set rfds, wfds;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000337 std::list<Socket*> sockets;
338 std::list<Socket*>::iterator i;
339
340 // Process any incoming X events
341 TXWindow::handleXEvents(dpy);
342
343 FD_ZERO(&rfds);
Pierre Ossman16419cc2016-04-29 14:29:43 +0200344 FD_ZERO(&wfds);
345
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000346 FD_SET(ConnectionNumber(dpy), &rfds);
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200347 for (std::list<TcpListener*>::iterator i = listeners.begin();
Tim Waugh892d10a2015-03-11 13:12:07 +0000348 i != listeners.end();
349 i++)
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200350 FD_SET((*i)->getFd(), &rfds);
Tim Waugh892d10a2015-03-11 13:12:07 +0000351
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000352 server.getSockets(&sockets);
353 int clients_connected = 0;
354 for (i = sockets.begin(); i != sockets.end(); i++) {
355 if ((*i)->isShutdown()) {
356 server.removeSocket(*i);
357 delete (*i);
358 } else {
359 FD_SET((*i)->getFd(), &rfds);
Pierre Ossman16419cc2016-04-29 14:29:43 +0200360 if ((*i)->outStream().bufferUsage() > 0)
361 FD_SET((*i)->getFd(), &wfds);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000362 clients_connected++;
363 }
364 }
365
Constantin Kaplinsky813dbb42006-12-05 08:03:18 +0000366 if (!clients_connected)
367 sched.reset();
368
Pierre Ossman278e4202016-04-29 14:28:54 +0200369 wait_ms = 0;
370
Constantin Kaplinsky813dbb42006-12-05 08:03:18 +0000371 if (sched.isRunning()) {
Pierre Ossman278e4202016-04-29 14:28:54 +0200372 wait_ms = sched.millisRemaining();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000373 if (wait_ms > 500) {
374 wait_ms = 500;
375 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000376 }
Pierre Ossman278e4202016-04-29 14:28:54 +0200377
378 soonestTimeout(&wait_ms, server.checkTimeouts());
379
380 tv.tv_sec = wait_ms / 1000;
381 tv.tv_usec = (wait_ms % 1000) * 1000;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000382
383 // Do the wait...
384 sched.sleepStarted();
Pierre Ossman16419cc2016-04-29 14:29:43 +0200385 int n = select(FD_SETSIZE, &rfds, &wfds, 0,
Pierre Ossman278e4202016-04-29 14:28:54 +0200386 wait_ms ? &tv : NULL);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000387 sched.sleepFinished();
388
389 if (n < 0) {
390 if (errno == EINTR) {
391 vlog.debug("Interrupted select() system call");
392 continue;
393 } else {
394 throw rdr::SystemException("select", errno);
395 }
396 }
397
398 // Accept new VNC connections
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200399 for (std::list<TcpListener*>::iterator i = listeners.begin();
Tim Waugh892d10a2015-03-11 13:12:07 +0000400 i != listeners.end();
401 i++) {
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200402 if (FD_ISSET((*i)->getFd(), &rfds)) {
403 Socket* sock = (*i)->accept();
Tim Waugh892d10a2015-03-11 13:12:07 +0000404 if (sock) {
Pierre Ossman16419cc2016-04-29 14:29:43 +0200405 sock->outStream().setBlocking(false);
Tim Waugh892d10a2015-03-11 13:12:07 +0000406 server.addSocket(sock);
407 } else {
408 vlog.status("Client connection rejected");
409 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000410 }
411 }
412
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000413 server.checkTimeouts();
414
415 // Client list could have been changed.
416 server.getSockets(&sockets);
417
418 // Nothing more to do if there are no client connections.
419 if (sockets.empty())
420 continue;
421
422 // Process events on existing VNC connections
423 for (i = sockets.begin(); i != sockets.end(); i++) {
424 if (FD_ISSET((*i)->getFd(), &rfds))
Pierre Ossmand408ca52016-04-29 14:26:05 +0200425 server.processSocketReadEvent(*i);
Pierre Ossman16419cc2016-04-29 14:29:43 +0200426 if (FD_ISSET((*i)->getFd(), &wfds))
427 server.processSocketWriteEvent(*i);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000428 }
429
430 if (desktop.isRunning() && sched.goodTimeToPoll()) {
431 sched.newPass();
432 desktop.poll();
433 }
434 }
435
436 } catch (rdr::Exception &e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000437 vlog.error("%s", e.str());
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000438 return 1;
439 }
440
Constantin Kaplinsky0c4306c2008-09-05 07:13:55 +0000441 TXWindow::handleXEvents(dpy);
442
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000443 vlog.info("Terminated");
444 return 0;
445}