/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright 2011 Pierre Ossman for Cendio AB * Copyright 2012 Samuel Mannehed for Cendio AB * * 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 #ifdef HAVE_GNUTLS #include #endif #ifdef _WIN32 #include #include #endif #include "parameters.h" #include #include #include #include #include #include #include #include #include "i18n.h" using namespace rfb; static LogWriter vlog("Parameters"); IntParameter pointerEventInterval("PointerEventInterval", "Time in milliseconds to rate-limit" " successive pointer events", 0); BoolParameter dotWhenNoCursor("DotWhenNoCursor", "Show the dot cursor when the server sends an " "invisible cursor", false); StringParameter passwordFile("PasswordFile", "Password file for VNC authentication", ""); AliasParameter passwd("passwd", "Alias for PasswordFile", &passwordFile); BoolParameter autoSelect("AutoSelect", "Auto select pixel format and encoding. " "Default if PreferredEncoding and FullColor are not specified.", true); BoolParameter fullColour("FullColor", "Use full color", true); AliasParameter fullColourAlias("FullColour", "Alias for FullColor", &fullColour); IntParameter lowColourLevel("LowColorLevel", "Color level to use on slow connections. " "0 = Very Low (8 colors), 1 = Low (64 colors), " "2 = Medium (256 colors)", 2); AliasParameter lowColourLevelAlias("LowColourLevel", "Alias for LowColorLevel", &lowColourLevel); StringParameter preferredEncoding("PreferredEncoding", "Preferred encoding to use (Tight, ZRLE, Hextile or" " Raw)", "Tight"); BoolParameter customCompressLevel("CustomCompressLevel", "Use custom compression level. " "Default if CompressLevel is specified.", false); IntParameter compressLevel("CompressLevel", "Use specified compression level 0 = Low, 6 = High", 2); BoolParameter noJpeg("NoJPEG", "Disable lossy JPEG compression in Tight encoding.", false); IntParameter qualityLevel("QualityLevel", "JPEG quality level. 0 = Low, 9 = High", 8); BoolParameter maximize("Maximize", "Maximize viewer window", false); #ifdef HAVE_FLTK_FULLSCREEN BoolParameter fullScreen("FullScreen", "Full screen mode", false); #ifdef HAVE_FLTK_FULLSCREEN_SCREENS BoolParameter fullScreenAllMonitors("FullScreenAllMonitors", "Enable full screen over all monitors", true); #endif // HAVE_FLTK_FULLSCREEN_SCREENS #endif // HAVE_FLTK_FULLSCREEN StringParameter desktopSize("DesktopSize", "Reconfigure desktop size on the server on " "connect (if possible)", ""); StringParameter geometry("geometry", "Specify size and position of viewer window", ""); BoolParameter listenMode("listen", "Listen for connections from VNC servers", false); BoolParameter remoteResize("RemoteResize", "Dynamically resize the remote desktop size as " "the size of the local client window changes. " "(Does not work with all servers)", true); BoolParameter viewOnly("ViewOnly", "Don't send any mouse or keyboard events to the server", false); BoolParameter shared("Shared", "Don't disconnect other viewers upon connection - " "share the desktop instead", false); BoolParameter acceptClipboard("AcceptClipboard", "Accept clipboard changes from the server", true); BoolParameter sendClipboard("SendClipboard", "Send clipboard changes to the server", true); BoolParameter sendPrimary("SendPrimary", "Send the primary selection and cut buffer to the " "server as well as the clipboard selection", true); StringParameter menuKey("MenuKey", "The key which brings up the popup menu", "F8"); BoolParameter fullscreenSystemKeys("FullscreenSystemKeys", "Pass special keys (like Alt+Tab) directly " "to the server when in full screen mode.", true); #ifndef WIN32 StringParameter via("via", "Gateway to tunnel via", ""); #endif const char* IDENTIFIER_STRING = "TigerVNC Configuration file Version 1.0"; VoidParameter* parameterArray[] = { #ifdef HAVE_GNUTLS &CSecurityTLS::X509CA, &CSecurityTLS::X509CRL, #endif // HAVE_GNUTLS &SecurityClient::secTypes, &dotWhenNoCursor, &autoSelect, &fullColour, &lowColourLevel, &preferredEncoding, &customCompressLevel, &compressLevel, &noJpeg, &qualityLevel, #ifdef HAVE_FLTK_FULLSCREEN &fullScreen, #ifdef HAVE_FLTK_FULLSCREEN_SCREENS &fullScreenAllMonitors, #endif // HAVE_FLTK_FULLSCREEN_SCREENS #endif // HAVE_FLTK_FULLSCREEN &desktopSize, &geometry, &remoteResize, &viewOnly, &shared, &acceptClipboard, &sendClipboard, &sendPrimary, &menuKey, &fullscreenSystemKeys }; // Encoding Table static struct { const char first; const char second; } replaceMap[] = {'\n', 'n', '\r', 'r'}; bool encodeValue(const char* val, char* dest, size_t destSize) { bool normalCharacter = true; size_t pos = 0; for (int i = 0; (val[i] != '\0') && (i < (destSize - 1)); i++) { // Check for sequences which will need encoding if (val[i] == '\\') { strncpy(dest+pos, "\\\\", 2); pos++; if (pos >= destSize) { vlog.error(_("Encoding backslash: The size of the buffer dest " "is to small, it needs to be more than %d bytes bigger."), (destSize - 1 - i)); return false; } } else { for (int j = 0; j < sizeof(replaceMap)/sizeof(replaceMap[0]); j++) { if (val[i] == replaceMap[j].first) { dest[pos] = '\\'; pos++; if (pos >= destSize) { vlog.error(_("Encoding escape sequence: The size of the buffer " "dest is to small, it needs to be more than %d bytes " "bigger."), (destSize - 1 - i)); return false; } dest[pos] = replaceMap[j].second; normalCharacter = false; break; } if (normalCharacter) { dest[pos] = val[i]; } } } normalCharacter = true; // Reset for next loop pos++; if (pos >= destSize) { vlog.error(_("Encoding normal character: The size of the buffer dest " "is to small, it needs to be more than %d bytes bigger."), (destSize - 1 - i)); return false; } } dest[pos] = '\0'; return true; } bool decodeValue(const char* val, char* dest, size_t destSize) { size_t pos = 0; bool escapedCharacter = false; for (int i = 0; (val[i] != '\0') && (i < (destSize - 1)); i++) { // Check for escape sequences if (val[i] == '\\') { for (int j = 0; j < sizeof(replaceMap)/sizeof(replaceMap[0]); j++) { if (val[i+1] == replaceMap[j].second) { dest[pos] = replaceMap[j].first; escapedCharacter = true; pos--; break; } } if (!escapedCharacter) { if (val[i+1] == '\\') { dest[pos] = val[i]; i++; } else { vlog.error(_("Unknown escape sequence at character %d"), i); return false; } } } else { dest[pos] = val[i]; } escapedCharacter = false; // Reset for next loop pos++; if (pos >= destSize) { vlog.error(_("Decoding: The size of the buffer dest is to small, " "it needs to be 1 byte bigger.")); return false; } } dest[pos] = '\0'; return true; } #ifdef _WIN32 void setKeyString(const char *_name, const char *_value, HKEY* hKey) { const DWORD buffersize = 256; wchar_t name[buffersize]; unsigned size = fl_utf8towc(_name, strlen(_name)+1, name, buffersize); if (size >= buffersize) { vlog.error(_("Could not convert the parameter-name %s to wchar_t* when " "writing to the Registry, the buffersize is to small."), _name); return; } char encodingBuffer[buffersize]; if (!encodeValue(_value, encodingBuffer, buffersize)) { vlog.error(_("Could not encode the parameter-value %s when " "writing to the Registry."), _value); return; } wchar_t value[buffersize]; size = fl_utf8towc(encodingBuffer, strlen(encodingBuffer)+1, value, buffersize); if (size >= buffersize) { vlog.error(_("Could not convert the parameter-value %s to wchar_t* when " "writing to the Registry, the buffersize is to small."), _value); return; } LONG res = RegSetValueExW(*hKey, name, 0, REG_SZ, (BYTE*)&value, (wcslen(value)+1)*2); if (res != ERROR_SUCCESS) { vlog.error(_("Error(%d) writing %s(REG_SZ) to Registry."), res, _value); return; } } void setKeyInt(const char *_name, const int _value, HKEY* hKey) { const DWORD buffersize = 256; wchar_t name[buffersize]; DWORD value = _value; unsigned size = fl_utf8towc(_name, strlen(_name)+1, name, buffersize); if (size >= buffersize) { vlog.error(_("Could not convert the parameter-name %s to wchar_t* when " "writing to the Registry, the buffersize is to small."), _name); return; } LONG res = RegSetValueExW(*hKey, name, 0, REG_DWORD, (BYTE*)&value, sizeof(DWORD)); if (res != ERROR_SUCCESS) { vlog.error(_("Error(%d) writing %d(REG_DWORD) to Registry."), res, _value); return; } } bool getKeyString(const char* _name, char* dest, size_t destSize, HKEY* hKey) { DWORD buffersize = 256; WCHAR value[destSize]; wchar_t name[buffersize]; unsigned size = fl_utf8towc(_name, strlen(_name)+1, name, buffersize); if (size >= buffersize) { vlog.error(_("Could not convert the parameter-name %s to wchar_t* when " "reading from the Registry, the buffersize is to small."), _name); return false; } LONG res = RegQueryValueExW(*hKey, name, 0, NULL, (LPBYTE)value, &buffersize); if (res != ERROR_SUCCESS){ if (res == ERROR_FILE_NOT_FOUND) { // The value does not exist, defaults will be used. } else { vlog.error(_("Error(%d) reading %s from Registry."), res, _name); } return false; } char utf8val[destSize]; size = fl_utf8fromwc(utf8val, sizeof(utf8val), value, wcslen(value)+1); if (size >= sizeof(utf8val)) { vlog.error(_("Could not convert the parameter-value for %s to utf8 " "char* when reading from the Registry, the buffer dest is " "to small."), _name); return false; } const char *ret = utf8val; if(decodeValue(ret, dest, destSize)) return true; else return false; } bool getKeyInt(const char* _name, int* dest, HKEY* hKey) { const DWORD buffersize = 256; DWORD dwordsize = sizeof(DWORD); DWORD value = 0; wchar_t name[buffersize]; unsigned size = fl_utf8towc(_name, strlen(_name)+1, name, buffersize); if (size >= buffersize) { vlog.error(_("Could not convert the parameter-name %s to wchar_t* when " "reading from the Registry, the buffersize is to small."), _name); return false; } LONG res = RegQueryValueExW(*hKey, name, 0, NULL, (LPBYTE)&value, &dwordsize); if (res != ERROR_SUCCESS){ if (res == ERROR_FILE_NOT_FOUND) { // The value does not exist, defaults will be used. } else { vlog.error(_("Error(%d) reading %s from Registry."), res, _name); } return false; } *dest = (int)value; return true; } void saveToReg(const char* servername) { HKEY hKey; LONG res = RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\TigerVNC\\vncviewer", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, NULL); if (res != ERROR_SUCCESS) { vlog.error(_("Error(%d) creating key: Software\\TigerVNC\\vncviewer"), res); return; } setKeyString("ServerName", servername, &hKey); for (int i = 0; i < sizeof(parameterArray)/sizeof(VoidParameter*); i++) { if (dynamic_cast(parameterArray[i]) != NULL) { setKeyString(parameterArray[i]->getName(), *(StringParameter*)parameterArray[i], &hKey); } else if (dynamic_cast(parameterArray[i]) != NULL) { setKeyInt(parameterArray[i]->getName(), (int)*(IntParameter*)parameterArray[i], &hKey); } else if (dynamic_cast(parameterArray[i]) != NULL) { setKeyInt(parameterArray[i]->getName(), (int)*(BoolParameter*)parameterArray[i], &hKey); } else { vlog.info(_("The parameterArray contains a object of a invalid type " "at line %d."), i); } } res = RegCloseKey(hKey); if (res != ERROR_SUCCESS) { vlog.error(_("Error(%d) closing key: Software\\TigerVNC\\vncviewer"), res); } } char* loadFromReg() { HKEY hKey; LONG res = RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\TigerVNC\\vncviewer", 0, KEY_READ, &hKey); if (res != ERROR_SUCCESS) { if (res == ERROR_FILE_NOT_FOUND) { // The key does not exist, defaults will be used. } else { vlog.error(_("Error(%d) opening key: Software\\TigerVNC\\vncviewer"), res); } return NULL; } const size_t buffersize = 256; static char servername[buffersize]; char servernameBuffer[buffersize]; if (getKeyString("ServerName", servernameBuffer, buffersize, &hKey)) snprintf(servername, buffersize, "%s", servernameBuffer); int intValue = 0; char stringValue[buffersize]; for (int i = 0; i < sizeof(parameterArray)/sizeof(VoidParameter*); i++) { if (dynamic_cast(parameterArray[i]) != NULL) { if (getKeyString(parameterArray[i]->getName(), stringValue, buffersize, &hKey)) parameterArray[i]->setParam(stringValue); } else if (dynamic_cast(parameterArray[i]) != NULL) { if (getKeyInt(parameterArray[i]->getName(), &intValue, &hKey)) ((IntParameter*)parameterArray[i])->setParam(intValue); } else if (dynamic_cast(parameterArray[i]) != NULL) { if (getKeyInt(parameterArray[i]->getName(), &intValue, &hKey)) ((BoolParameter*)parameterArray[i])->setParam(intValue); } else { vlog.info(_("The parameterArray contains a object of a invalid type at line %d."), i); } } res = RegCloseKey(hKey); if (res != ERROR_SUCCESS){ vlog.error(_("Error(%d) closing key: Software\\TigerVNC\\vncviewer"), res); } return servername; } #endif // _WIN32 void saveViewerParameters(const char *filename, const char *servername) { const size_t buffersize = 256; char filepath[PATH_MAX]; char write_error[buffersize*2]; char encodingBuffer[buffersize]; // Write to the registry or a predefined file if no filename was specified. if(filename == NULL) { #ifdef _WIN32 saveToReg(servername); return; #endif char* homeDir = NULL; if (getvnchomedir(&homeDir) == -1) { vlog.error(_("Failed to write configuration file, can't obtain home " "directory path.")); return; } snprintf(filepath, sizeof(filepath), "%sdefault.tigervnc", homeDir); } else { snprintf(filepath, sizeof(filepath), "%s", filename); } /* Write parameters to file */ FILE* f = fopen(filepath, "w+"); if (!f) { snprintf(write_error, sizeof(write_error), _("Failed to write configuration file, can't open %s"), filepath); throw Exception(write_error); } fprintf(f, "%s\r\n", IDENTIFIER_STRING); fprintf(f, "\r\n"); if (encodeValue(servername, encodingBuffer, buffersize)) fprintf(f, "ServerName=%s\n", encodingBuffer); for (int i = 0; i < sizeof(parameterArray)/sizeof(VoidParameter*); i++) { if (dynamic_cast(parameterArray[i]) != NULL) { if (encodeValue(*(StringParameter*)parameterArray[i], encodingBuffer, buffersize)) fprintf(f, "%s=%s\n", ((StringParameter*)parameterArray[i])->getName(), encodingBuffer); } else if (dynamic_cast(parameterArray[i]) != NULL) { fprintf(f, "%s=%d\n", ((IntParameter*)parameterArray[i])->getName(), (int)*(IntParameter*)parameterArray[i]); } else if (dynamic_cast(parameterArray[i]) != NULL) { fprintf(f, "%s=%d\n", ((BoolParameter*)parameterArray[i])->getName(), (int)*(BoolParameter*)parameterArray[i]); } else { vlog.info(_("The parameterArray contains a object of a invalid type " "at line %d."), i); } } fclose(f); } char* loadViewerParameters(const char *filename) { const size_t buffersize = 256; char filepath[PATH_MAX]; char readError[buffersize*2]; char line[buffersize]; char decodingBuffer[buffersize]; char decodedValue[buffersize]; static char servername[sizeof(line)]; // Load from the registry or a predefined file if no filename was specified. if(filename == NULL) { #ifdef _WIN32 return loadFromReg(); #endif char* homeDir = NULL; if (getvnchomedir(&homeDir) == -1) throw Exception(_("Failed to read configuration file, " "can't obtain home directory path.")); snprintf(filepath, sizeof(filepath), "%sdefault.tigervnc", homeDir); } else { snprintf(filepath, sizeof(filepath), "%s", filename); } /* Read parameters from file */ FILE* f = fopen(filepath, "r"); if (!f) { if (!filename) return NULL; // Use defaults. snprintf(readError, sizeof(readError), _("Failed to read configuration file, can't open %s"), filepath); throw Exception(readError); } int lineNr = 0; while (!feof(f)) { // Read the next line lineNr++; if (!fgets(line, sizeof(line), f)) { if (line[sizeof(line) -1] != '\0') { vlog.error(_("Could not read the line(%d) in the configuration file," "the buffersize is to small."), lineNr); return NULL; } if (feof(f)) break; snprintf(readError, sizeof(readError), _("Failed to read line %d in file %s"), lineNr, filepath); throw Exception(readError); } // Make sure that the first line of the file has the file identifier string if(lineNr == 1) { if(strncmp(line, IDENTIFIER_STRING, strlen(IDENTIFIER_STRING)) == 0) { continue; } else { snprintf(readError, sizeof(readError), _("Line 1 in file %s\nmust contain the TigerVNC " "configuration file identifier string:\n" "\"%s\""), filepath, IDENTIFIER_STRING); throw Exception(readError); } } // Skip empty lines and comments if ((line[0] == '\n') || (line[0] == '#') || (line[0] == '\r')) continue; int len = strlen(line); if (line[len-1] == '\n') { line[len-1] = '\0'; len--; } // Find the parameter value char *value = strchr(line, '='); if (value == NULL) { vlog.info(_("Bad Name/Value pair on line: %d in file: %s"), lineNr, filepath); continue; } *value = '\0'; // line only contains the parameter name below. value++; bool invalidParameterName = true; // Will be set to false below if // the line contains a valid name. if (strcasecmp(line, "ServerName") == 0) { if(!decodeValue(value, decodingBuffer, sizeof(decodingBuffer))) { vlog.info(_("The value of the parameter %s on line %d in file %s " "is invalid."), line, lineNr, filepath); continue; } snprintf(servername, sizeof(decodingBuffer), "%s", decodingBuffer); invalidParameterName = false; } else { // Find and set the correct parameter for (int i = 0; i < sizeof(parameterArray)/sizeof(VoidParameter*); i++) { if (dynamic_cast(parameterArray[i]) != NULL) { if (strcasecmp(line, ((StringParameter*)parameterArray[i])->getName()) == 0) { if(!decodeValue(value, decodingBuffer, sizeof(decodingBuffer))) { vlog.info(_("The value of the parameter %s on line %d in file %s " "is invalid."), line, lineNr, filepath); continue; } ((StringParameter*)parameterArray[i])->setParam(decodingBuffer); invalidParameterName = false; } } else if (dynamic_cast(parameterArray[i]) != NULL) { if (strcasecmp(line, ((IntParameter*)parameterArray[i])->getName()) == 0) { ((IntParameter*)parameterArray[i])->setParam(atoi(value)); invalidParameterName = false; } } else if (dynamic_cast(parameterArray[i]) != NULL) { if (strcasecmp(line, ((BoolParameter*)parameterArray[i])->getName()) == 0) { ((BoolParameter*)parameterArray[i])->setParam(atoi(value)); invalidParameterName = false; } } else { vlog.info(_("The parameterArray contains a object of a invalid type " "at line %d."), lineNr); } } } if (invalidParameterName) vlog.info(_("Invalid parameter name on line: %d in file: %s"), lineNr, filepath); } fclose(f); f=0; return servername; }