blob: 05abd704f529edfc7ff7ca52d83f81d1ddae387c [file] [log] [blame] [edit]
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
* Copyright 2011 Pierre Ossman <ossman@cendio.se> for Cendio AB
* Copyright 2012 Samuel Mannehed <samuel@cendio.se> 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 <config.h>
#endif
#ifdef HAVE_GNUTLS
#include <rfb/CSecurityTLS.h>
#endif
#ifdef _WIN32
#include <windows.h>
#include <tchar.h>
#endif
#include "parameters.h"
#include <os/os.h>
#include <rfb/Exception.h>
#include <rfb/LogWriter.h>
#include <rfb/SecurityClient.h>
#include <FL/fl_utf8.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include "i18n.h"
using namespace rfb;
static LogWriter vlog("Parameters");
IntParameter pointerEventInterval("PointerEventInterval",
"Time in milliseconds to rate-limit"
" successive pointer events", 17);
BoolParameter dotWhenNoCursor("DotWhenNoCursor",
"Show the dot cursor when the server sends an "
"invisible cursor", false);
BoolParameter alertOnFatalError("AlertOnFatalError",
"Give a dialog on connection problems rather "
"than exiting immediately", true);
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);
BoolParameter fullScreen("FullScreen", "Full screen mode", false);
BoolParameter fullScreenAllMonitors("FullScreenAllMonitors",
"Enable full screen over all monitors",
true);
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);
#if !defined(WIN32) && !defined(__APPLE__)
BoolParameter setPrimary("SetPrimary",
"Set the primary selection as well as the "
"clipboard selection", true);
BoolParameter sendPrimary("SendPrimary",
"Send the primary selection to the "
"server as well as the clipboard selection",
true);
StringParameter display("display",
"Specifies the X display on which the VNC viewer window should appear.",
"");
#endif
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
static const char* IDENTIFIER_STRING = "TigerVNC Configuration file Version 1.0";
static VoidParameter* parameterArray[] = {
#ifdef HAVE_GNUTLS
&CSecurityTLS::X509CA,
&CSecurityTLS::X509CRL,
#endif // HAVE_GNUTLS
&SecurityClient::secTypes,
&dotWhenNoCursor,
&autoSelect,
&fullColour,
&lowColourLevel,
&preferredEncoding,
&customCompressLevel,
&compressLevel,
&noJpeg,
&qualityLevel,
&fullScreen,
&fullScreenAllMonitors,
&desktopSize,
&remoteResize,
&viewOnly,
&shared,
&acceptClipboard,
&sendClipboard,
#if !defined(WIN32) && !defined(__APPLE__)
&sendPrimary,
&setPrimary,
#endif
&menuKey,
&fullscreenSystemKeys
};
// Encoding Table
static struct {
const char first;
const char second;
} replaceMap[] = { { '\n', 'n' },
{ '\r', 'r' },
{ '\\', '\\' } };
static bool encodeValue(const char* val, char* dest, size_t destSize) {
size_t pos = 0;
for (size_t i = 0; (val[i] != '\0') && (i < (destSize - 1)); i++) {
bool normalCharacter;
// Check for sequences which will need encoding
normalCharacter = true;
for (size_t j = 0; j < sizeof(replaceMap)/sizeof(replaceMap[0]); j++) {
if (val[i] == replaceMap[j].first) {
dest[pos] = '\\';
pos++;
if (pos >= destSize)
return false;
dest[pos] = replaceMap[j].second;
normalCharacter = false;
break;
}
if (normalCharacter) {
dest[pos] = val[i];
}
}
pos++;
if (pos >= destSize)
return false;
}
dest[pos] = '\0';
return true;
}
static bool decodeValue(const char* val, char* dest, size_t destSize) {
size_t pos = 0;
for (size_t i = 0; (val[i] != '\0') && (i < (destSize - 1)); i++) {
// Check for escape sequences
if (val[i] == '\\') {
bool escapedCharacter;
escapedCharacter = false;
for (size_t j = 0; j < sizeof(replaceMap)/sizeof(replaceMap[0]); j++) {
if (val[i+1] == replaceMap[j].second) {
dest[pos] = replaceMap[j].first;
escapedCharacter = true;
i++;
break;
}
}
if (!escapedCharacter)
return false;
} else {
dest[pos] = val[i];
}
pos++;
if (pos >= destSize) {
return false;
}
}
dest[pos] = '\0';
return true;
}
#ifdef _WIN32
static 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(_("The name of the parameter %s was too large to write to the registry"), _name);
return;
}
char encodingBuffer[buffersize];
if (!encodeValue(_value, encodingBuffer, buffersize)) {
vlog.error(_("The parameter %s was too large to write to the registry"), _name);
return;
}
wchar_t value[buffersize];
size = fl_utf8towc(encodingBuffer, strlen(encodingBuffer)+1, value, buffersize);
if (size >= buffersize) {
vlog.error(_("The parameter %s was too large to write to the registry"), _name);
return;
}
LONG res = RegSetValueExW(*hKey, name, 0, REG_SZ, (BYTE*)&value, (wcslen(value)+1)*2);
if (res != ERROR_SUCCESS) {
vlog.error(_("Failed to write parameter %s of type %s to the registry: %ld"),
_name, "REG_SZ", res);
return;
}
}
static 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(_("The name of the parameter %s was too large to write to the registry"), _name);
return;
}
LONG res = RegSetValueExW(*hKey, name, 0, REG_DWORD, (BYTE*)&value, sizeof(DWORD));
if (res != ERROR_SUCCESS) {
vlog.error(_("Failed to write parameter %s of type %s to the registry: %ld"),
_name, "REG_DWORD", res);
return;
}
}
static bool getKeyString(const char* _name, char* dest, size_t destSize, HKEY* hKey) {
const DWORD buffersize = 256;
wchar_t name[buffersize];
WCHAR* value;
DWORD valuesize;
unsigned size = fl_utf8towc(_name, strlen(_name)+1, name, buffersize);
if (size >= buffersize) {
vlog.error(_("The name of the parameter %s was too large to read from the registry"), _name);
return false;
}
value = new WCHAR[destSize];
valuesize = destSize;
LONG res = RegQueryValueExW(*hKey, name, 0, NULL, (LPBYTE)value, &valuesize);
if (res != ERROR_SUCCESS){
delete [] value;
if (res == ERROR_FILE_NOT_FOUND) {
// The value does not exist, defaults will be used.
} else {
vlog.error(_("Failed to read parameter %s from the registry: %ld"),
_name, res);
}
return false;
}
char* utf8val = new char[destSize];
size = fl_utf8fromwc(utf8val, destSize, value, wcslen(value)+1);
delete [] value;
if (size >= destSize) {
delete [] utf8val;
vlog.error(_("The parameter %s was too large to read from the registry"), _name);
return false;
}
bool ret = decodeValue(utf8val, dest, destSize);
delete [] utf8val;
return ret;
}
static 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(_("The name of the parameter %s was too large to read from the registry"), _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(_("Failed to read parameter %s from the registry: %ld"),
_name, res);
}
return false;
}
*dest = (int)value;
return true;
}
static 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(_("Failed to create registry key: %ld"), res);
return;
}
setKeyString("ServerName", servername, &hKey);
for (size_t i = 0; i < sizeof(parameterArray)/sizeof(VoidParameter*); i++) {
if (dynamic_cast<StringParameter*>(parameterArray[i]) != NULL) {
setKeyString(parameterArray[i]->getName(), *(StringParameter*)parameterArray[i], &hKey);
} else if (dynamic_cast<IntParameter*>(parameterArray[i]) != NULL) {
setKeyInt(parameterArray[i]->getName(), (int)*(IntParameter*)parameterArray[i], &hKey);
} else if (dynamic_cast<BoolParameter*>(parameterArray[i]) != NULL) {
setKeyInt(parameterArray[i]->getName(), (int)*(BoolParameter*)parameterArray[i], &hKey);
} else {
vlog.error(_("Unknown parameter type for parameter %s"),
parameterArray[i]->getName());
}
}
res = RegCloseKey(hKey);
if (res != ERROR_SUCCESS) {
vlog.error(_("Failed to close registry key: %ld"), res);
}
}
static 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(_("Failed to open registry key: %ld"), 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 (size_t i = 0; i < sizeof(parameterArray)/sizeof(VoidParameter*); i++) {
if (dynamic_cast<StringParameter*>(parameterArray[i]) != NULL) {
if (getKeyString(parameterArray[i]->getName(), stringValue, buffersize, &hKey))
parameterArray[i]->setParam(stringValue);
} else if (dynamic_cast<IntParameter*>(parameterArray[i]) != NULL) {
if (getKeyInt(parameterArray[i]->getName(), &intValue, &hKey))
((IntParameter*)parameterArray[i])->setParam(intValue);
} else if (dynamic_cast<BoolParameter*>(parameterArray[i]) != NULL) {
if (getKeyInt(parameterArray[i]->getName(), &intValue, &hKey))
((BoolParameter*)parameterArray[i])->setParam(intValue);
} else {
vlog.error(_("Unknown parameter type for parameter %s"),
parameterArray[i]->getName());
}
}
res = RegCloseKey(hKey);
if (res != ERROR_SUCCESS){
vlog.error(_("Failed to close registry key: %ld"), res);
}
return servername;
}
#endif // _WIN32
void saveViewerParameters(const char *filename, const char *servername) {
const size_t buffersize = 256;
char filepath[PATH_MAX];
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);
delete[] homeDir;
} else {
snprintf(filepath, sizeof(filepath), "%s", filename);
}
/* Write parameters to file */
FILE* f = fopen(filepath, "w+");
if (!f)
throw Exception(_("Failed to write configuration file, can't open %s: %s"),
filepath, strerror(errno));
fprintf(f, "%s\r\n", IDENTIFIER_STRING);
fprintf(f, "\r\n");
if (encodeValue(servername, encodingBuffer, buffersize))
fprintf(f, "ServerName=%s\n", encodingBuffer);
for (size_t i = 0; i < sizeof(parameterArray)/sizeof(VoidParameter*); i++) {
if (dynamic_cast<StringParameter*>(parameterArray[i]) != NULL) {
if (encodeValue(*(StringParameter*)parameterArray[i], encodingBuffer, buffersize))
fprintf(f, "%s=%s\n", ((StringParameter*)parameterArray[i])->getName(), encodingBuffer);
} else if (dynamic_cast<IntParameter*>(parameterArray[i]) != NULL) {
fprintf(f, "%s=%d\n", ((IntParameter*)parameterArray[i])->getName(), (int)*(IntParameter*)parameterArray[i]);
} else if (dynamic_cast<BoolParameter*>(parameterArray[i]) != NULL) {
fprintf(f, "%s=%d\n", ((BoolParameter*)parameterArray[i])->getName(), (int)*(BoolParameter*)parameterArray[i]);
} else {
vlog.error(_("Unknown parameter type for parameter %s"),
parameterArray[i]->getName());
}
}
fclose(f);
}
char* loadViewerParameters(const char *filename) {
const size_t buffersize = 256;
char filepath[PATH_MAX];
char line[buffersize];
char decodingBuffer[buffersize];
static char servername[sizeof(line)];
memset(servername, '\0', sizeof(servername));
// 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);
delete[] 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.
throw Exception(_("Failed to read configuration file, can't open %s: %s"),
filepath, strerror(errno));
}
int lineNr = 0;
while (!feof(f)) {
// Read the next line
lineNr++;
if (!fgets(line, sizeof(line), f)) {
if (feof(f))
break;
throw Exception(_("Failed to read line %d in file %s: %s"),
lineNr, filepath, strerror(errno));
}
if (strlen(line) == (sizeof(line) - 1))
throw Exception(_("Failed to read line %d in file %s: %s"),
lineNr, filepath, _("Line too long"));
// 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
throw Exception(_("Configuration file %s is in an invalid format"),
filepath);
}
// 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.error(_("Failed to read line %d in file %s: %s"),
lineNr, filepath, _("Invalid format"));
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.error(_("Failed to read line %d in file %s: %s"),
lineNr, filepath, _("Invalid format or too large value"));
continue;
}
snprintf(servername, sizeof(decodingBuffer), "%s", decodingBuffer);
invalidParameterName = false;
} else {
// Find and set the correct parameter
for (size_t i = 0; i < sizeof(parameterArray)/sizeof(VoidParameter*); i++) {
if (dynamic_cast<StringParameter*>(parameterArray[i]) != NULL) {
if (strcasecmp(line, ((StringParameter*)parameterArray[i])->getName()) == 0) {
if(!decodeValue(value, decodingBuffer, sizeof(decodingBuffer))) {
vlog.error(_("Failed to read line %d in file %s: %s"),
lineNr, filepath, _("Invalid format or too large value"));
continue;
}
((StringParameter*)parameterArray[i])->setParam(decodingBuffer);
invalidParameterName = false;
}
} else if (dynamic_cast<IntParameter*>(parameterArray[i]) != NULL) {
if (strcasecmp(line, ((IntParameter*)parameterArray[i])->getName()) == 0) {
((IntParameter*)parameterArray[i])->setParam(atoi(value));
invalidParameterName = false;
}
} else if (dynamic_cast<BoolParameter*>(parameterArray[i]) != NULL) {
if (strcasecmp(line, ((BoolParameter*)parameterArray[i])->getName()) == 0) {
((BoolParameter*)parameterArray[i])->setParam(atoi(value));
invalidParameterName = false;
}
} else {
vlog.error(_("Unknown parameter type for parameter %s"),
parameterArray[i]->getName());
}
}
}
if (invalidParameterName)
vlog.info(_("Unknown parameter %s on line %d in file %s"),
line, lineNr, filepath);
}
fclose(f); f=0;
return servername;
}