/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright (C) 2004-2008 Constantin Kaplinsky. All Rights Reserved. * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. */ // FIXME: Check cases when screen width/height is not a multiply of 32. // e.g. 800x600. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_XTEST #include #endif #ifdef HAVE_XDAMAGE #include #endif #include #include #include #include // XXX Lynx/OS 2.3: protos for select(), bzero() #ifdef Lynx #include #endif extern char buildtime[]; using namespace rfb; using namespace network; static LogWriter vlog("Main"); IntParameter pollingCycle("PollingCycle", "Milliseconds per one polling " "cycle; actual interval may be dynamically " "adjusted to satisfy MaxProcessorUsage setting", 30); IntParameter maxProcessorUsage("MaxProcessorUsage", "Maximum percentage of " "CPU time to be consumed", 35); BoolParameter useShm("UseSHM", "Use MIT-SHM extension if available", true); BoolParameter useOverlay("OverlayMode", "Use overlay mode under " "IRIX or Solaris", true); StringParameter displayname("display", "The X display", ""); IntParameter rfbport("rfbport", "TCP port to listen for RFB protocol",5900); IntParameter queryConnectTimeout("QueryConnectTimeout", "Number of seconds to show the Accept Connection dialog before " "rejecting the connection", 10); StringParameter hostsFile("HostsFile", "File with IP access control rules", ""); // // Allow the main loop terminate itself gracefully on receiving a signal. // static bool caughtSignal = false; static void CleanupSignalHandler(int sig) { caughtSignal = true; } class QueryConnHandler : public VNCServerST::QueryConnectionHandler, public QueryResultCallback { public: QueryConnHandler(Display* dpy, VNCServerST* vs) : display(dpy), server(vs), queryConnectDialog(0), queryConnectSock(0) {} ~QueryConnHandler() { delete queryConnectDialog; } // -=- VNCServerST::QueryConnectionHandler interface virtual VNCServerST::queryResult queryConnection(network::Socket* sock, const char* userName, char** reason) { if (queryConnectSock) { *reason = strDup("Another connection is currently being queried."); return VNCServerST::REJECT; } if (!userName) userName = "(anonymous)"; queryConnectSock = sock; CharArray address(sock->getPeerAddress()); delete queryConnectDialog; queryConnectDialog = new QueryConnectDialog(display, address.buf, userName, queryConnectTimeout, this); queryConnectDialog->map(); return VNCServerST::PENDING; } // -=- QueryResultCallback interface virtual void queryApproved() { server->approveConnection(queryConnectSock, true, 0); queryConnectSock = 0; } virtual void queryRejected() { server->approveConnection(queryConnectSock, false, "Connection rejected by local user"); queryConnectSock = 0; } private: Display* display; VNCServerST* server; QueryConnectDialog* queryConnectDialog; network::Socket* queryConnectSock; }; class XDesktop : public SDesktop, public TXGlobalEventHandler { public: XDesktop(Display* dpy_, Geometry *geometry_) : dpy(dpy_), geometry(geometry_), pb(0), server(0), oldButtonMask(0), haveXtest(false), haveDamage(false), maxButtons(0), running(false) { #ifdef HAVE_XTEST int xtestEventBase; int xtestErrorBase; int major, minor; if (XTestQueryExtension(dpy, &xtestEventBase, &xtestErrorBase, &major, &minor)) { XTestGrabControl(dpy, True); vlog.info("XTest extension present - version %d.%d",major,minor); haveXtest = true; } else { #endif vlog.info("XTest extension not present"); vlog.info("Unable to inject events or display while server is grabbed"); #ifdef HAVE_XTEST } #endif #ifdef HAVE_XDAMAGE int xdamageErrorBase; if (XDamageQueryExtension(dpy, &xdamageEventBase, &xdamageErrorBase)) { TXWindow::setGlobalEventHandler(this); haveDamage = true; } else { #endif vlog.info("DAMAGE extension not present"); vlog.info("Will have to poll screen for changes"); #ifdef HAVE_XDAMAGE } #endif } virtual ~XDesktop() { stop(); } inline void poll() { if (pb and not haveDamage) pb->poll(server); } // -=- SDesktop interface virtual void start(VNCServer* vs) { // Determine actual number of buttons of the X pointer device. unsigned char btnMap[8]; int numButtons = XGetPointerMapping(dpy, btnMap, 8); maxButtons = (numButtons > 8) ? 8 : numButtons; vlog.info("Enabling %d button%s of X pointer device", maxButtons, (maxButtons != 1) ? "s" : ""); // Create an ImageFactory instance for producing Image objects. ImageFactory factory((bool)useShm, (bool)useOverlay); // Create pixel buffer and provide it to the server object. pb = new XPixelBuffer(dpy, factory, geometry->getRect()); vlog.info("Allocated %s", pb->getImage()->classDesc()); server = (VNCServerST *)vs; server->setPixelBuffer(pb); #ifdef HAVE_XDAMAGE if (haveDamage) { damage = XDamageCreate(dpy, DefaultRootWindow(dpy), XDamageReportRawRectangles); } #endif running = true; } virtual void stop() { running = false; #ifdef HAVE_XDAMAGE if (haveDamage) XDamageDestroy(dpy, damage); #endif delete pb; pb = 0; } inline bool isRunning() { return running; } virtual void pointerEvent(const Point& pos, int buttonMask) { #ifdef HAVE_XTEST if (!haveXtest) return; XTestFakeMotionEvent(dpy, DefaultScreen(dpy), geometry->offsetLeft() + pos.x, geometry->offsetTop() + pos.y, CurrentTime); if (buttonMask != oldButtonMask) { for (int i = 0; i < maxButtons; i++) { if ((buttonMask ^ oldButtonMask) & (1<width(), pb->height()); } // -=- TXGlobalEventHandler interface virtual bool handleGlobalEvent(XEvent* ev) { #ifdef HAVE_XDAMAGE XDamageNotifyEvent* dev; Rect rect; if (ev->type != xdamageEventBase) return false; if (!running) return true; dev = (XDamageNotifyEvent*)ev; rect.setXYWH(dev->area.x, dev->area.y, dev->area.width, dev->area.height); server->add_changed(rect); return true; #endif } protected: Display* dpy; Geometry* geometry; XPixelBuffer* pb; VNCServerST* server; int oldButtonMask; bool haveXtest; bool haveDamage; int maxButtons; bool running; #ifdef HAVE_XDAMAGE Damage damage; int xdamageEventBase; #endif }; class FileTcpFilter : public TcpFilter { public: FileTcpFilter(const char *fname) : TcpFilter("-"), fileName(NULL), lastModTime(0) { if (fname != NULL) fileName = strdup((char *)fname); } virtual ~FileTcpFilter() { if (fileName != NULL) free(fileName); } virtual bool verifyConnection(Socket* s) { if (!reloadRules()) { vlog.error("Could not read IP filtering rules: rejecting all clients"); filter.clear(); filter.push_back(parsePattern("-")); return false; } return TcpFilter::verifyConnection(s); } protected: bool reloadRules() { if (fileName == NULL) return true; struct stat st; if (stat(fileName, &st) != 0) return false; if (st.st_mtime != lastModTime) { // Actually reload only if the file was modified FILE *fp = fopen(fileName, "r"); if (fp == NULL) return false; // Remove all the rules from the parent class filter.clear(); // Parse the file contents adding rules to the parent class char buf[32]; while (readLine(buf, 32, fp)) { if (buf[0] && strchr("+-?", buf[0])) { filter.push_back(parsePattern(buf)); } } fclose(fp); lastModTime = st.st_mtime; } return true; } protected: char *fileName; time_t lastModTime; private: // // NOTE: we silently truncate long lines in this function. // bool readLine(char *buf, int bufSize, FILE *fp) { if (fp == NULL || buf == NULL || bufSize == 0) return false; if (fgets(buf, bufSize, fp) == NULL) return false; char *ptr = strchr(buf, '\n'); if (ptr != NULL) { *ptr = '\0'; // remove newline at the end } else { if (!feof(fp)) { int c; do { // skip the rest of a long line c = getc(fp); } while (c != '\n' && c != EOF); } } return true; } }; char* programName; static void printVersion(FILE *fp) { fprintf(fp, "TigerVNC Server version %s, built %s\n", PACKAGE_VERSION, buildtime); } static void usage() { printVersion(stderr); fprintf(stderr, "\nUsage: %s []\n", programName); fprintf(stderr, " %s --version\n", programName); fprintf(stderr,"\n" "Parameters can be turned on with - or off with -=0\n" "Parameters which take a value can be specified as " "- \n" "Other valid forms are = -= " "--=\n" "Parameter names are case-insensitive. The parameters are:\n\n"); Configuration::listParams(79, 14); exit(1); } int main(int argc, char** argv) { initStdIOLoggers(); LogWriter::setLogParams("*:stderr:30"); programName = argv[0]; Display* dpy; Configuration::enableServerParams(); for (int i = 1; i < argc; i++) { if (Configuration::setParam(argv[i])) continue; if (argv[i][0] == '-') { if (i+1 < argc) { if (Configuration::setParam(&argv[i][1], argv[i+1])) { i++; continue; } } if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "-version") == 0 || strcmp(argv[i], "--version") == 0) { printVersion(stdout); return 0; } usage(); } usage(); } CharArray dpyStr(displayname.getData()); if (!(dpy = XOpenDisplay(dpyStr.buf[0] ? dpyStr.buf : 0))) { // FIXME: Why not vlog.error(...)? fprintf(stderr,"%s: unable to open display \"%s\"\r\n", programName, XDisplayName(dpyStr.buf)); exit(1); } signal(SIGHUP, CleanupSignalHandler); signal(SIGINT, CleanupSignalHandler); signal(SIGTERM, CleanupSignalHandler); try { TXWindow::init(dpy,"x0vncserver"); Geometry geo(DisplayWidth(dpy, DefaultScreen(dpy)), DisplayHeight(dpy, DefaultScreen(dpy))); if (geo.getRect().is_empty()) { vlog.error("Exiting with error"); return 1; } XDesktop desktop(dpy, &geo); VNCServerST server("x0vncserver", &desktop); QueryConnHandler qcHandler(dpy, &server); server.setQueryConnectionHandler(&qcHandler); TcpListener listener(NULL, (int)rfbport); vlog.info("Listening on port %d", (int)rfbport); const char *hostsData = hostsFile.getData(); FileTcpFilter fileTcpFilter(hostsData); if (strlen(hostsData) != 0) listener.setFilter(&fileTcpFilter); delete[] hostsData; PollingScheduler sched((int)pollingCycle, (int)maxProcessorUsage); while (!caughtSignal) { struct timeval tv; fd_set rfds; std::list sockets; std::list::iterator i; // Process any incoming X events TXWindow::handleXEvents(dpy); FD_ZERO(&rfds); FD_SET(ConnectionNumber(dpy), &rfds); FD_SET(listener.getFd(), &rfds); server.getSockets(&sockets); int clients_connected = 0; for (i = sockets.begin(); i != sockets.end(); i++) { if ((*i)->isShutdown()) { server.removeSocket(*i); delete (*i); } else { FD_SET((*i)->getFd(), &rfds); clients_connected++; } } if (!clients_connected) sched.reset(); if (sched.isRunning()) { int wait_ms = sched.millisRemaining(); if (wait_ms > 500) { wait_ms = 500; } tv.tv_usec = wait_ms * 1000; #ifdef DEBUG // fprintf(stderr, "[%d]\t", wait_ms); #endif } else { tv.tv_usec = 100000; } tv.tv_sec = 0; // Do the wait... sched.sleepStarted(); int n = select(FD_SETSIZE, &rfds, 0, 0, &tv); sched.sleepFinished(); if (n < 0) { if (errno == EINTR) { vlog.debug("Interrupted select() system call"); continue; } else { throw rdr::SystemException("select", errno); } } // Accept new VNC connections if (FD_ISSET(listener.getFd(), &rfds)) { Socket* sock = listener.accept(); if (sock) { server.addSocket(sock); } else { vlog.status("Client connection rejected"); } } Timer::checkTimeouts(); server.checkTimeouts(); // Client list could have been changed. server.getSockets(&sockets); // Nothing more to do if there are no client connections. if (sockets.empty()) continue; // Process events on existing VNC connections for (i = sockets.begin(); i != sockets.end(); i++) { if (FD_ISSET((*i)->getFd(), &rfds)) server.processSocketEvent(*i); } if (desktop.isRunning() && sched.goodTimeToPoll()) { sched.newPass(); desktop.poll(); } } } catch (rdr::Exception &e) { vlog.error("%s", e.str()); return 1; } TXWindow::handleXEvents(dpy); vlog.info("Terminated"); return 0; }