blob: 744f802ef4fece64b8bd361b409d213b7ce75c14 [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);
59BoolParameter useShm("UseSHM", "Use MIT-SHM extension if available", true);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000060StringParameter displayname("display", "The X display", "");
61IntParameter rfbport("rfbport", "TCP port to listen for RFB protocol",5900);
62IntParameter queryConnectTimeout("QueryConnectTimeout",
63 "Number of seconds to show the Accept Connection dialog before "
64 "rejecting the connection",
65 10);
66StringParameter hostsFile("HostsFile", "File with IP access control rules", "");
Peter Korsgaard8fe50902017-07-13 00:35:57 +020067BoolParameter rawKeyboard("RawKeyboard",
68 "Send keyboard events straight through and "
69 "avoid mapping them to the current keyboard "
70 "layout", false);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000071
72//
73// Allow the main loop terminate itself gracefully on receiving a signal.
74//
75
76static bool caughtSignal = false;
77
78static void CleanupSignalHandler(int sig)
79{
80 caughtSignal = true;
81}
82
83
84class QueryConnHandler : public VNCServerST::QueryConnectionHandler,
85 public QueryResultCallback {
86public:
87 QueryConnHandler(Display* dpy, VNCServerST* vs)
88 : display(dpy), server(vs), queryConnectDialog(0), queryConnectSock(0) {}
89 ~QueryConnHandler() { delete queryConnectDialog; }
90
91 // -=- VNCServerST::QueryConnectionHandler interface
92 virtual VNCServerST::queryResult queryConnection(network::Socket* sock,
93 const char* userName,
94 char** reason) {
95 if (queryConnectSock) {
Adam Tkacd36b6262009-09-04 10:57:20 +000096 *reason = strDup("Another connection is currently being queried.");
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000097 return VNCServerST::REJECT;
98 }
99 if (!userName) userName = "(anonymous)";
100 queryConnectSock = sock;
101 CharArray address(sock->getPeerAddress());
102 delete queryConnectDialog;
103 queryConnectDialog = new QueryConnectDialog(display, address.buf,
104 userName, queryConnectTimeout,
105 this);
106 queryConnectDialog->map();
107 return VNCServerST::PENDING;
108 }
109
110 // -=- QueryResultCallback interface
111 virtual void queryApproved() {
112 server->approveConnection(queryConnectSock, true, 0);
113 queryConnectSock = 0;
114 }
115 virtual void queryRejected() {
116 server->approveConnection(queryConnectSock, false,
117 "Connection rejected by local user");
118 queryConnectSock = 0;
119 }
120private:
121 Display* display;
122 VNCServerST* server;
123 QueryConnectDialog* queryConnectDialog;
124 network::Socket* queryConnectSock;
125};
126
127
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000128class FileTcpFilter : public TcpFilter
129{
130
131public:
132
133 FileTcpFilter(const char *fname)
134 : TcpFilter("-"), fileName(NULL), lastModTime(0)
135 {
136 if (fname != NULL)
137 fileName = strdup((char *)fname);
138 }
139
140 virtual ~FileTcpFilter()
141 {
142 if (fileName != NULL)
143 free(fileName);
144 }
145
146 virtual bool verifyConnection(Socket* s)
147 {
148 if (!reloadRules()) {
149 vlog.error("Could not read IP filtering rules: rejecting all clients");
150 filter.clear();
151 filter.push_back(parsePattern("-"));
152 return false;
153 }
154
155 return TcpFilter::verifyConnection(s);
156 }
157
158protected:
159
160 bool reloadRules()
161 {
162 if (fileName == NULL)
163 return true;
164
165 struct stat st;
166 if (stat(fileName, &st) != 0)
167 return false;
168
169 if (st.st_mtime != lastModTime) {
170 // Actually reload only if the file was modified
171 FILE *fp = fopen(fileName, "r");
172 if (fp == NULL)
173 return false;
174
175 // Remove all the rules from the parent class
176 filter.clear();
177
178 // Parse the file contents adding rules to the parent class
179 char buf[32];
180 while (readLine(buf, 32, fp)) {
181 if (buf[0] && strchr("+-?", buf[0])) {
182 filter.push_back(parsePattern(buf));
183 }
184 }
185
186 fclose(fp);
187 lastModTime = st.st_mtime;
188 }
189 return true;
190 }
191
192protected:
193
194 char *fileName;
195 time_t lastModTime;
196
197private:
198
199 //
200 // NOTE: we silently truncate long lines in this function.
201 //
202
203 bool readLine(char *buf, int bufSize, FILE *fp)
204 {
205 if (fp == NULL || buf == NULL || bufSize == 0)
206 return false;
207
208 if (fgets(buf, bufSize, fp) == NULL)
209 return false;
210
211 char *ptr = strchr(buf, '\n');
212 if (ptr != NULL) {
213 *ptr = '\0'; // remove newline at the end
214 } else {
215 if (!feof(fp)) {
216 int c;
217 do { // skip the rest of a long line
218 c = getc(fp);
219 } while (c != '\n' && c != EOF);
220 }
221 }
222 return true;
223 }
224
225};
226
227char* programName;
228
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000229static void printVersion(FILE *fp)
230{
Peter Åstrand4eacc022009-02-27 10:12:14 +0000231 fprintf(fp, "TigerVNC Server version %s, built %s\n",
Constantin Kaplinskyea7b6502008-09-28 05:08:48 +0000232 PACKAGE_VERSION, buildtime);
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000233}
234
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000235static void usage()
236{
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000237 printVersion(stderr);
238 fprintf(stderr, "\nUsage: %s [<parameters>]\n", programName);
239 fprintf(stderr, " %s --version\n", programName);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000240 fprintf(stderr,"\n"
241 "Parameters can be turned on with -<param> or off with -<param>=0\n"
242 "Parameters which take a value can be specified as "
243 "-<param> <value>\n"
244 "Other valid forms are <param>=<value> -<param>=<value> "
245 "--<param>=<value>\n"
246 "Parameter names are case-insensitive. The parameters are:\n\n");
247 Configuration::listParams(79, 14);
248 exit(1);
249}
250
251int main(int argc, char** argv)
252{
253 initStdIOLoggers();
254 LogWriter::setLogParams("*:stderr:30");
255
256 programName = argv[0];
257 Display* dpy;
258
Adam Tkacc58b3d12010-04-23 13:55:10 +0000259 Configuration::enableServerParams();
260
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000261 for (int i = 1; i < argc; i++) {
262 if (Configuration::setParam(argv[i]))
263 continue;
264
265 if (argv[i][0] == '-') {
266 if (i+1 < argc) {
267 if (Configuration::setParam(&argv[i][1], argv[i+1])) {
268 i++;
269 continue;
270 }
271 }
Constantin Kaplinsky2039d7b2008-06-04 10:43:10 +0000272 if (strcmp(argv[i], "-v") == 0 ||
273 strcmp(argv[i], "-version") == 0 ||
274 strcmp(argv[i], "--version") == 0) {
275 printVersion(stdout);
276 return 0;
277 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000278 usage();
279 }
280
281 usage();
282 }
283
284 CharArray dpyStr(displayname.getData());
285 if (!(dpy = XOpenDisplay(dpyStr.buf[0] ? dpyStr.buf : 0))) {
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000286 // FIXME: Why not vlog.error(...)?
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000287 fprintf(stderr,"%s: unable to open display \"%s\"\r\n",
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000288 programName, XDisplayName(dpyStr.buf));
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000289 exit(1);
290 }
291
292 signal(SIGHUP, CleanupSignalHandler);
293 signal(SIGINT, CleanupSignalHandler);
294 signal(SIGTERM, CleanupSignalHandler);
295
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200296 std::list<TcpListener*> listeners;
Tim Waugh892d10a2015-03-11 13:12:07 +0000297
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000298 try {
299 TXWindow::init(dpy,"x0vncserver");
300 Geometry geo(DisplayWidth(dpy, DefaultScreen(dpy)),
301 DisplayHeight(dpy, DefaultScreen(dpy)));
Constantin Kaplinsky23c60222008-06-04 03:58:07 +0000302 if (geo.getRect().is_empty()) {
303 vlog.error("Exiting with error");
304 return 1;
305 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000306 XDesktop desktop(dpy, &geo);
Constantin Kaplinsky82328312008-04-24 08:44:24 +0000307
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000308 VNCServerST server("x0vncserver", &desktop);
309 QueryConnHandler qcHandler(dpy, &server);
310 server.setQueryConnectionHandler(&qcHandler);
311
Tim Waugh892d10a2015-03-11 13:12:07 +0000312 createTcpListeners(&listeners, 0, (int)rfbport);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000313 vlog.info("Listening on port %d", (int)rfbport);
314
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000315 const char *hostsData = hostsFile.getData();
316 FileTcpFilter fileTcpFilter(hostsData);
317 if (strlen(hostsData) != 0)
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200318 for (std::list<TcpListener*>::iterator i = listeners.begin();
Tim Waugh892d10a2015-03-11 13:12:07 +0000319 i != listeners.end();
320 i++)
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200321 (*i)->setFilter(&fileTcpFilter);
Constantin Kaplinsky7bdccd72008-08-20 06:22:28 +0000322 delete[] hostsData;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000323
324 PollingScheduler sched((int)pollingCycle, (int)maxProcessorUsage);
325
326 while (!caughtSignal) {
Pierre Ossman278e4202016-04-29 14:28:54 +0200327 int wait_ms;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000328 struct timeval tv;
Pierre Ossman16419cc2016-04-29 14:29:43 +0200329 fd_set rfds, wfds;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000330 std::list<Socket*> sockets;
331 std::list<Socket*>::iterator i;
332
333 // Process any incoming X events
334 TXWindow::handleXEvents(dpy);
335
336 FD_ZERO(&rfds);
Pierre Ossman16419cc2016-04-29 14:29:43 +0200337 FD_ZERO(&wfds);
338
Pierre Ossmana7b728a2014-06-13 10:56:59 +0000339 FD_SET(ConnectionNumber(dpy), &rfds);
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200340 for (std::list<TcpListener*>::iterator i = listeners.begin();
Tim Waugh892d10a2015-03-11 13:12:07 +0000341 i != listeners.end();
342 i++)
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200343 FD_SET((*i)->getFd(), &rfds);
Tim Waugh892d10a2015-03-11 13:12:07 +0000344
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000345 server.getSockets(&sockets);
346 int clients_connected = 0;
347 for (i = sockets.begin(); i != sockets.end(); i++) {
348 if ((*i)->isShutdown()) {
349 server.removeSocket(*i);
350 delete (*i);
351 } else {
352 FD_SET((*i)->getFd(), &rfds);
Pierre Ossman16419cc2016-04-29 14:29:43 +0200353 if ((*i)->outStream().bufferUsage() > 0)
354 FD_SET((*i)->getFd(), &wfds);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000355 clients_connected++;
356 }
357 }
358
Constantin Kaplinsky813dbb42006-12-05 08:03:18 +0000359 if (!clients_connected)
360 sched.reset();
361
Pierre Ossman278e4202016-04-29 14:28:54 +0200362 wait_ms = 0;
363
Constantin Kaplinsky813dbb42006-12-05 08:03:18 +0000364 if (sched.isRunning()) {
Pierre Ossman278e4202016-04-29 14:28:54 +0200365 wait_ms = sched.millisRemaining();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000366 if (wait_ms > 500) {
367 wait_ms = 500;
368 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000369 }
Pierre Ossman278e4202016-04-29 14:28:54 +0200370
371 soonestTimeout(&wait_ms, server.checkTimeouts());
372
373 tv.tv_sec = wait_ms / 1000;
374 tv.tv_usec = (wait_ms % 1000) * 1000;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000375
376 // Do the wait...
377 sched.sleepStarted();
Pierre Ossman16419cc2016-04-29 14:29:43 +0200378 int n = select(FD_SETSIZE, &rfds, &wfds, 0,
Pierre Ossman278e4202016-04-29 14:28:54 +0200379 wait_ms ? &tv : NULL);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000380 sched.sleepFinished();
381
382 if (n < 0) {
383 if (errno == EINTR) {
384 vlog.debug("Interrupted select() system call");
385 continue;
386 } else {
387 throw rdr::SystemException("select", errno);
388 }
389 }
390
391 // Accept new VNC connections
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200392 for (std::list<TcpListener*>::iterator i = listeners.begin();
Tim Waugh892d10a2015-03-11 13:12:07 +0000393 i != listeners.end();
394 i++) {
Pierre Ossmanf7aa3f92015-09-29 15:40:49 +0200395 if (FD_ISSET((*i)->getFd(), &rfds)) {
396 Socket* sock = (*i)->accept();
Tim Waugh892d10a2015-03-11 13:12:07 +0000397 if (sock) {
Pierre Ossman16419cc2016-04-29 14:29:43 +0200398 sock->outStream().setBlocking(false);
Tim Waugh892d10a2015-03-11 13:12:07 +0000399 server.addSocket(sock);
400 } else {
401 vlog.status("Client connection rejected");
402 }
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000403 }
404 }
405
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000406 server.checkTimeouts();
407
408 // Client list could have been changed.
409 server.getSockets(&sockets);
410
411 // Nothing more to do if there are no client connections.
412 if (sockets.empty())
413 continue;
414
415 // Process events on existing VNC connections
416 for (i = sockets.begin(); i != sockets.end(); i++) {
417 if (FD_ISSET((*i)->getFd(), &rfds))
Pierre Ossmand408ca52016-04-29 14:26:05 +0200418 server.processSocketReadEvent(*i);
Pierre Ossman16419cc2016-04-29 14:29:43 +0200419 if (FD_ISSET((*i)->getFd(), &wfds))
420 server.processSocketWriteEvent(*i);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000421 }
422
423 if (desktop.isRunning() && sched.goodTimeToPoll()) {
424 sched.newPass();
425 desktop.poll();
426 }
427 }
428
429 } catch (rdr::Exception &e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000430 vlog.error("%s", e.str());
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000431 return 1;
432 }
433
Constantin Kaplinsky0c4306c2008-09-05 07:13:55 +0000434 TXWindow::handleXEvents(dpy);
435
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000436 vlog.info("Terminated");
437 return 0;
438}