/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright 2011 Pierre Ossman for Cendio AB * Copyright (C) 2011 D. R. Commander. 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. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #ifdef WIN32 #include #include #define mkdir(path, mode) _mkdir(path) #endif #if !defined(WIN32) && !defined(__APPLE__) #include #include #endif #include #include #include #ifdef HAVE_GNUTLS #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "i18n.h" #include "parameters.h" #include "CConn.h" #include "ServerDialog.h" #include "UserDialog.h" #include "vncviewer.h" #include "fltk_layout.h" #ifdef WIN32 #include "resource.h" #include "win32.h" #endif rfb::LogWriter vlog("main"); using namespace network; using namespace rfb; using namespace std; char vncServerName[VNCSERVERNAMELEN] = { '\0' }; static const char *argv0 = NULL; static bool exitMainloop = false; static const char *exitError = NULL; static const char *about_text() { static char buffer[1024]; // This is used in multiple places with potentially different // encodings, so we need to make sure we get a fresh string every // time. snprintf(buffer, sizeof(buffer), _("TigerVNC Viewer %d-bit v%s\n" "Built on: %s\n" "Copyright (C) 1999-%d TigerVNC Team and many others (see README.rst)\n" "See http://www.tigervnc.org for information on TigerVNC."), (int)sizeof(size_t)*8, PACKAGE_VERSION, BUILD_TIMESTAMP, 2017); return buffer; } void exit_vncviewer(const char *error) { // Prioritise the first error we get as that is probably the most // relevant one. if ((error != NULL) && (exitError == NULL)) exitError = strdup(error); exitMainloop = true; } bool should_exit() { return exitMainloop; } void about_vncviewer() { fl_message_title(_("About TigerVNC Viewer")); fl_message("%s", about_text()); } void run_mainloop() { int next_timer; next_timer = Timer::checkTimeouts(); if (next_timer == 0) next_timer = INT_MAX; if (Fl::wait((double)next_timer / 1000.0) < 0.0) { vlog.error(_("Internal FLTK error. Exiting.")); exit(-1); } } #ifdef __APPLE__ static void about_callback(Fl_Widget *widget, void *data) { about_vncviewer(); } static void new_connection_cb(Fl_Widget *widget, void *data) { const char *argv[2]; pid_t pid; pid = fork(); if (pid == -1) { vlog.error(_("Error starting new TigerVNC Viewer: %s"), strerror(errno)); return; } if (pid != 0) return; argv[0] = argv0; argv[1] = NULL; execvp(argv[0], (char * const *)argv); vlog.error(_("Error starting new TigerVNC Viewer: %s"), strerror(errno)); _exit(1); } #endif static void CleanupSignalHandler(int sig) { // CleanupSignalHandler allows C++ object cleanup to happen because it calls // exit() rather than the default which is to abort. vlog.info(_("Termination signal %d has been received. TigerVNC Viewer will now exit."), sig); exit(1); } static void init_fltk() { // Basic text size (10pt @ 96 dpi => 13px) FL_NORMAL_SIZE = 13; // Select a FLTK scheme and background color that looks somewhat // close to modern systems Fl::scheme("gtk+"); Fl::background(220, 220, 220); // macOS has a slightly brighter default background though #ifdef __APPLE__ Fl::background(240, 240, 240); #endif // Proper Gnome Shell integration requires that we set a sensible // WM_CLASS for the window. Fl_Window::default_xclass("vncviewer"); // Set the default icon for all windows. #ifdef WIN32 HICON lg, sm; lg = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR | LR_SHARED); sm = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR | LR_SHARED); Fl_Window::default_icons(lg, sm); #elif ! defined(__APPLE__) const int icon_sizes[] = {48, 32, 24, 16}; Fl_PNG_Image *icons[4]; int count; count = 0; // FIXME: Follow icon theme specification for (size_t i = 0;i < sizeof(icon_sizes)/sizeof(icon_sizes[0]);i++) { char icon_path[PATH_MAX]; bool exists; sprintf(icon_path, "%s/icons/hicolor/%dx%d/apps/tigervnc.png", DATA_DIR, icon_sizes[i], icon_sizes[i]); #ifndef WIN32 struct stat st; if (stat(icon_path, &st) != 0) #else struct _stat st; if (_stat(icon_path, &st) != 0) return(false); #endif exists = false; else exists = true; if (exists) { icons[count] = new Fl_PNG_Image(icon_path); if (icons[count]->w() == 0 || icons[count]->h() == 0 || icons[count]->d() != 4) { delete icons[count]; continue; } count++; } } Fl_Window::default_icons((const Fl_RGB_Image**)icons, count); for (int i = 0;i < count;i++) delete icons[i]; #endif // This makes the "icon" in dialogs rounded, which fits better // with the above schemes. fl_message_icon()->box(FL_UP_BOX); // Turn off the annoying behaviour where popups track the mouse. fl_message_hotspot(false); // Avoid empty titles for popups fl_message_title_default(_("TigerVNC Viewer")); #ifdef WIN32 // Most "normal" Windows apps use this font for UI elements. Fl::set_font(FL_HELVETICA, "Tahoma"); #endif // FLTK exposes these so that we can translate them. fl_no = _("No"); fl_yes = _("Yes"); fl_ok = _("OK"); fl_cancel = _("Cancel"); fl_close = _("Close"); #ifdef __APPLE__ /* Needs trailing space */ static char fltk_about[16]; snprintf(fltk_about, sizeof(fltk_about), "%s ", _("About")); Fl_Mac_App_Menu::about = fltk_about; static char fltk_hide[16]; snprintf(fltk_hide, sizeof(fltk_hide), "%s ", _("Hide")); Fl_Mac_App_Menu::hide = fltk_hide; static char fltk_quit[16]; snprintf(fltk_quit, sizeof(fltk_quit), "%s ", _("Quit")); Fl_Mac_App_Menu::quit = fltk_quit; Fl_Mac_App_Menu::print = ""; // Don't want the print item Fl_Mac_App_Menu::services = _("Services"); Fl_Mac_App_Menu::hide_others = _("Hide Others"); Fl_Mac_App_Menu::show = _("Show All"); fl_mac_set_about(about_callback, NULL); Fl_Sys_Menu_Bar *menubar; char buffer[1024]; menubar = new Fl_Sys_Menu_Bar(0, 0, 500, 25); // Fl_Sys_Menu_Bar overrides methods without them being virtual, // which means we cannot use our generic Fl_Menu_ helpers. if (fltk_menu_escape(p_("SysMenu|", "&File"), buffer, sizeof(buffer)) < sizeof(buffer)) menubar->add(buffer, 0, 0, 0, FL_SUBMENU); if (fltk_menu_escape(p_("SysMenu|File|", "&New Connection"), buffer, sizeof(buffer)) < sizeof(buffer)) menubar->insert(1, buffer, FL_COMMAND | 'n', new_connection_cb); #endif } static void mkvnchomedir() { // Create .vnc in the user's home directory if it doesn't already exist char* homeDir = NULL; if (getvnchomedir(&homeDir) == -1) { vlog.error(_("Could not create VNC home directory: can't obtain home " "directory path.")); } else { int result = mkdir(homeDir, 0755); if (result == -1 && errno != EEXIST) vlog.error(_("Could not create VNC home directory: %s."), strerror(errno)); delete [] homeDir; } } static void usage(const char *programName) { #ifdef WIN32 // If we don't have a console then we need to create one for output if (GetConsoleWindow() == NULL) { HANDLE handle; int fd; AllocConsole(); handle = GetStdHandle(STD_ERROR_HANDLE); fd = _open_osfhandle((intptr_t)handle, O_TEXT); *stderr = *fdopen(fd, "w"); } #endif fprintf(stderr, "\nusage: %s [parameters] [host:displayNum] [parameters]\n" " %s [parameters] -listen [port] [parameters]\n", programName, 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); #ifdef WIN32 // Just wait for the user to kill the console window Sleep(INFINITE); #endif exit(1); } #ifndef WIN32 static int interpretViaParam(char *remoteHost, int *remotePort, int localPort) { const int SERVER_PORT_OFFSET = 5900; char *pos = strchr(vncServerName, ':'); if (pos == NULL) *remotePort = SERVER_PORT_OFFSET; else { int portOffset = SERVER_PORT_OFFSET; size_t len; *pos++ = '\0'; len = strlen(pos); if (*pos == ':') { /* Two colons is an absolute port number, not an offset. */ pos++; len--; portOffset = 0; } if (!len || strspn (pos, "-0123456789") != len ) return 1; *remotePort = atoi(pos) + portOffset; } if (*vncServerName != '\0') strncpy(remoteHost, vncServerName, VNCSERVERNAMELEN); else strncpy(remoteHost, "localhost", VNCSERVERNAMELEN); remoteHost[VNCSERVERNAMELEN - 1] = '\0'; snprintf(vncServerName, VNCSERVERNAMELEN, "localhost::%d", localPort); vncServerName[VNCSERVERNAMELEN - 1] = '\0'; return 0; } static void createTunnel(const char *gatewayHost, const char *remoteHost, int remotePort, int localPort) { const char *cmd = getenv("VNC_VIA_CMD"); char *cmd2, *percent; char lport[10], rport[10]; sprintf(lport, "%d", localPort); sprintf(rport, "%d", remotePort); setenv("G", gatewayHost, 1); setenv("H", remoteHost, 1); setenv("R", rport, 1); setenv("L", lport, 1); if (!cmd) cmd = "/usr/bin/ssh -f -L \"$L\":\"$H\":\"$R\" \"$G\" sleep 20"; /* Compatibility with TigerVNC's method. */ cmd2 = strdup(cmd); while ((percent = strchr(cmd2, '%')) != NULL) *percent = '$'; system(cmd2); free(cmd2); } static int mktunnel() { const char *gatewayHost; char remoteHost[VNCSERVERNAMELEN]; int localPort = findFreeTcpPort(); int remotePort; gatewayHost = strDup(via.getValueStr()); if (interpretViaParam(remoteHost, &remotePort, localPort) != 0) return 1; createTunnel(gatewayHost, remoteHost, remotePort, localPort); return 0; } #endif /* !WIN32 */ int main(int argc, char** argv) { UserDialog dlg; argv0 = argv[0]; setlocale(LC_ALL, ""); bindtextdomain(PACKAGE_NAME, LOCALE_DIR); textdomain(PACKAGE_NAME); rfb::SecurityClient::setDefaults(); // Write about text to console, still using normal locale codeset fprintf(stderr,"\n%s\n", about_text()); // Set gettext codeset to what our GUI toolkit uses. Since we are // passing strings from strerror/gai_strerror to the GUI, these must // be in GUI codeset as well. bind_textdomain_codeset(PACKAGE_NAME, "UTF-8"); bind_textdomain_codeset("libc", "UTF-8"); rfb::initStdIOLoggers(); #ifdef WIN32 rfb::initFileLogger("C:\\temp\\vncviewer.log"); #else rfb::initFileLogger("/tmp/vncviewer.log"); #endif rfb::LogWriter::setLogParams("*:stderr:30"); #ifdef SIGHUP signal(SIGHUP, CleanupSignalHandler); #endif signal(SIGINT, CleanupSignalHandler); signal(SIGTERM, CleanupSignalHandler); init_fltk(); Configuration::enableViewerParams(); /* Load the default parameter settings */ const char* defaultServerName; try { defaultServerName = loadViewerParameters(NULL); } catch (rfb::Exception& e) { defaultServerName = ""; vlog.error("%s", e.str()); if (alertOnFatalError) fl_alert("%s", e.str()); } for (int i = 1; i < argc;) { if (Configuration::setParam(argv[i])) { i++; continue; } if (argv[i][0] == '-') { if (i+1 < argc) { if (Configuration::setParam(&argv[i][1], argv[i+1])) { i += 2; continue; } } usage(argv[0]); } strncpy(vncServerName, argv[i], VNCSERVERNAMELEN); vncServerName[VNCSERVERNAMELEN - 1] = '\0'; i++; } mkvnchomedir(); #if !defined(WIN32) && !defined(__APPLE__) if (strcmp(display, "") != 0) { Fl::display(display); } fl_open_display(); XkbSetDetectableAutoRepeat(fl_display, True, NULL); #endif CSecurity::upg = &dlg; #ifdef HAVE_GNUTLS CSecurityTLS::msg = &dlg; #endif Socket *sock = NULL; #ifndef WIN32 /* Specifying -via and -listen together is nonsense */ if (listenMode && strlen(via.getValueStr()) > 0) { // TRANSLATORS: "Parameters" are command line arguments, or settings // from a file or the Windows registry. vlog.error(_("Parameters -listen and -via are incompatible")); if (alertOnFatalError) fl_alert(_("Parameters -listen and -via are incompatible")); exit_vncviewer(); return 1; } #endif if (listenMode) { std::list listeners; try { int port = 5500; if (isdigit(vncServerName[0])) port = atoi(vncServerName); createTcpListeners(&listeners, 0, port); vlog.info(_("Listening on port %d"), port); /* Wait for a connection */ while (sock == NULL) { fd_set rfds; FD_ZERO(&rfds); for (std::list::iterator i = listeners.begin(); i != listeners.end(); i++) FD_SET((*i)->getFd(), &rfds); int n = select(FD_SETSIZE, &rfds, 0, 0, 0); if (n < 0) { if (errno == EINTR) { vlog.debug("Interrupted select() system call"); continue; } else { throw rdr::SystemException("select", errno); } } for (std::list::iterator i = listeners.begin (); i != listeners.end(); i++) if (FD_ISSET((*i)->getFd(), &rfds)) { sock = (*i)->accept(); if (sock) /* Got a connection */ break; } } } catch (rdr::Exception& e) { vlog.error("%s", e.str()); if (alertOnFatalError) fl_alert("%s", e.str()); exit_vncviewer(); return 1; } while (!listeners.empty()) { delete listeners.back(); listeners.pop_back(); } } else { if (vncServerName[0] == '\0') { ServerDialog::run(defaultServerName, vncServerName); if (vncServerName[0] == '\0') return 1; } #ifndef WIN32 if (strlen (via.getValueStr()) > 0 && mktunnel() != 0) usage(argv[0]); #endif } CConn *cc = new CConn(vncServerName, sock); while (!exitMainloop) run_mainloop(); delete cc; if (exitError != NULL && alertOnFatalError) fl_alert("%s", exitError); return 0; }