/* Copyright (C) 2016 Brian P. Hinz * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. */ package com.tigervnc.vncviewer; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.LineNumberReader; import java.io.PrintWriter; import java.util.StringTokenizer; import com.tigervnc.rfb.*; import com.tigervnc.rfb.Exception; public class Parameters { public static BoolParameter noLionFS = new BoolParameter("NoLionFS", "On Mac systems, setting this parameter will force the use of the old "+ "(pre-Lion) full-screen mode, even if the viewer is running on OS X 10.7 "+ "Lion or later.", false); public static BoolParameter embed = new BoolParameter("Embed", "If the viewer is being run as an applet, display its output to " + "an embedded frame in the browser window rather than to a dedicated " + "window. Embed=1 implies FullScreen=0 and Scale=100.", false); public static BoolParameter useLocalCursor = new BoolParameter("UseLocalCursor", "Render the mouse cursor locally", true); public static BoolParameter sendLocalUsername = new BoolParameter("SendLocalUsername", "Send the local username for SecurityTypes "+ "such as Plain rather than prompting", true); public static StringParameter passwordFile = new StringParameter("PasswordFile", "Password file for VNC authentication", ""); public static AliasParameter passwd = new AliasParameter("passwd", "Alias for PasswordFile", passwordFile); public static BoolParameter autoSelect = new BoolParameter("AutoSelect", "Auto select pixel format and encoding", true); public static BoolParameter fullColor = new BoolParameter("FullColor", "Use full color - otherwise 6-bit colour is "+ "used until AutoSelect decides the link is "+ "fast enough", true); public static AliasParameter fullColorAlias = new AliasParameter("FullColour", "Alias for FullColor", Parameters.fullColor); public static IntParameter lowColorLevel = new IntParameter("LowColorLevel", "Color level to use on slow connections. "+ "0 = Very Low (8 colors), 1 = Low (64 colors), "+ "2 = Medium (256 colors)", 2); public static AliasParameter lowColorLevelAlias = new AliasParameter("LowColourLevel", "Alias for LowColorLevel", lowColorLevel); public static StringParameter preferredEncoding = new StringParameter("PreferredEncoding", "Preferred encoding to use (Tight, ZRLE, "+ "hextile or raw) - implies AutoSelect=0", "Tight"); public static BoolParameter viewOnly = new BoolParameter("ViewOnly", "Don't send any mouse or keyboard events to "+ "the server", false); public static BoolParameter shared = new BoolParameter("Shared", "Don't disconnect other viewers upon "+ "connection - share the desktop instead", false); public static BoolParameter fullScreen = new BoolParameter("FullScreen", "Full Screen Mode", false); public static BoolParameter fullScreenAllMonitors = new BoolParameter("FullScreenAllMonitors", "Enable full screen over all monitors", true); public static BoolParameter acceptClipboard = new BoolParameter("AcceptClipboard", "Accept clipboard changes from the server", true); public static BoolParameter sendClipboard = new BoolParameter("SendClipboard", "Send clipboard changes to the server", true); public static IntParameter maxCutText = new IntParameter("MaxCutText", "Maximum permitted length of an outgoing clipboard update", 262144); public static StringParameter menuKey = new StringParameter("MenuKey", "The key which brings up the popup menu", "F8"); public static StringParameter desktopSize = new StringParameter("DesktopSize", "Reconfigure desktop size on the server on "+ "connect (if possible)", ""); public static BoolParameter listenMode = new BoolParameter("listen", "Listen for connections from VNC servers", false); public static StringParameter scalingFactor = new StringParameter("ScalingFactor", "Reduce or enlarge the remote desktop image. "+ "The value is interpreted as a scaling factor "+ "in percent. If the parameter is set to "+ "\"Auto\", then automatic scaling is "+ "performed. Auto-scaling tries to choose a "+ "scaling factor in such a way that the whole "+ "remote desktop will fit on the local screen. "+ "If the parameter is set to \"FixedRatio\", "+ "then automatic scaling is performed, but the "+ "original aspect ratio is preserved.", "100"); public static BoolParameter alwaysShowServerDialog = new BoolParameter("AlwaysShowServerDialog", "Always show the server dialog even if a server "+ "has been specified in an applet parameter or on "+ "the command line", false); public static StringParameter vncServerName = new StringParameter("Server", "The VNC server [:] or "+ "::", ""); /* public static IntParameter vncServerPort = new IntParameter("Port", "The VNC server's port number, assuming it is on "+ "the host from which the applet was downloaded", 0); */ public static BoolParameter acceptBell = new BoolParameter("AcceptBell", "Produce a system beep when requested to by the server.", true); public static StringParameter via = new StringParameter("Via", "Automatically create an encrypted TCP tunnel to "+ "the gateway machine, then connect to the VNC host "+ "through that tunnel. By default, this option invokes "+ "SSH local port forwarding using the embedded JSch "+ "client, however an external SSH client may be specified "+ "using the \"-extSSH\" parameter. Note that when using "+ "the -via option, the VNC host machine name should be "+ "specified from the point of view of the gateway machine, "+ "e.g. \"localhost\" denotes the gateway, "+ "not the machine on which the viewer was launched. "+ "See the System Properties section below for "+ "information on configuring the -Via option.", ""); public static BoolParameter tunnel = new BoolParameter("Tunnel", "The -Tunnel command is basically a shorthand for the "+ "-via command when the VNC server and SSH gateway are "+ "one and the same. -Tunnel creates an SSH connection "+ "to the server and forwards the VNC through the tunnel "+ "without the need to specify anything else.", false); public static BoolParameter extSSH = new BoolParameter("extSSH", "By default, SSH tunneling uses the embedded JSch client "+ "for tunnel creation. This option causes the client to "+ "invoke an external SSH client application for all tunneling "+ "operations. By default, \"/usr/bin/ssh\" is used, however "+ "the path to the external application may be specified using "+ "the -SSHClient option.", false); public static StringParameter extSSHClient = new StringParameter("extSSHClient", "Specifies the path to an external SSH client application "+ "that is to be used for tunneling operations when the -extSSH "+ "option is in effect.", "/usr/bin/ssh"); public static StringParameter extSSHArgs = new StringParameter("extSSHArgs", "Specifies the arguments string or command template to be used "+ "by the external SSH client application when the -extSSH option "+ "is in effect. The string will be processed according to the same "+ "pattern substitution rules as the VNC_TUNNEL_CMD and VNC_VIA_CMD "+ "system properties, and can be used to override those in a more "+ "command-line friendly way. If not specified, then the appropriate "+ "VNC_TUNNEL_CMD or VNC_VIA_CMD command template will be used.", ""); public static StringParameter sshConfig = new StringParameter("SSHConfig", "Specifies the path to an OpenSSH configuration file that to "+ "be parsed by the embedded JSch SSH client during tunneling "+ "operations.", FileUtils.getHomeDir()+".ssh/config"); public static StringParameter sshKey = new StringParameter("SSHKey", "When using the Via or Tunnel options with the embedded SSH client, "+ "this parameter specifies the text of the SSH private key to use when "+ "authenticating with the SSH server. You can use \\n within the string "+ "to specify a new line.", ""); public static StringParameter sshKeyFile = new StringParameter("SSHKeyFile", "When using the Via or Tunnel options with the embedded SSH client, "+ "this parameter specifies a file that contains an SSH private key "+ "(or keys) to use when authenticating with the SSH server. If not "+ "specified, ~/.ssh/id_dsa or ~/.ssh/id_rsa will be used (if they exist). "+ "Otherwise, the client will fallback to prompting for an SSH password.", ""); public static StringParameter sshKeyPass = new StringParameter("SSHKeyPass", "When using the Via or Tunnel options with the embedded SSH client, "+ "this parameter specifies the passphrase for the SSH key.", ""); public static BoolParameter customCompressLevel = new BoolParameter("CustomCompressLevel", "Use custom compression level. "+ "Default if CompressLevel is specified.", false); public static IntParameter compressLevel = new IntParameter("CompressLevel", "Use specified compression level "+ "0 = Low, 6 = High", 1); public static BoolParameter noJpeg = new BoolParameter("NoJPEG", "Disable lossy JPEG compression in Tight encoding.", false); public static IntParameter qualityLevel = new IntParameter("QualityLevel", "JPEG quality level. "+ "0 = Low, 9 = High", 8); private static final String IDENTIFIER_STRING = "TigerVNC Configuration file Version 1.0"; static VoidParameter[] parameterArray = { CSecurityTLS.X509CA, CSecurityTLS.X509CRL, SecurityClient.secTypes, autoSelect, fullColor, lowColorLevel, preferredEncoding, customCompressLevel, compressLevel, noJpeg, qualityLevel, fullScreen, fullScreenAllMonitors, desktopSize, viewOnly, shared, acceptClipboard, sendClipboard, menuKey, noLionFS, useLocalCursor, sendLocalUsername, maxCutText, scalingFactor, acceptBell, via, tunnel, extSSH, extSSHClient, extSSHArgs, sshConfig, sshKeyFile, }; static LogWriter vlog = new LogWriter("Parameters"); public static void saveViewerParameters(String filename, String servername) { // Write to the registry or a predefined file if no filename was specified. String filepath; if (filename == null || filename.isEmpty()) { saveToReg(servername); return; /* String homeDir = FileUtils.getVncHomeDir(); if (homeDir == null) throw new Exception("Failed to read configuration file, "+ "can't obtain home directory path."); filepath = homeDir.concat("default.tigervnc"); */ } else { filepath = filename; } /* Write parameters to file */ File f = new File(filepath); if (f.exists() && !f.canWrite()) throw new Exception(String.format("Failed to write configuration file,"+ "can't open %s", filepath)); PrintWriter pw = null; try { pw = new PrintWriter(f, "UTF-8"); } catch (java.lang.Exception e) { throw new Exception(e.getMessage()); } pw.println(IDENTIFIER_STRING); pw.println(""); if (servername != null && !servername.isEmpty()) { pw.println(String.format("ServerName=%s\n", servername)); updateConnHistory(servername); } for (int i = 0; i < parameterArray.length; i++) { if (parameterArray[i] instanceof StringParameter) { //if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName())) pw.println(String.format("%s=%s",parameterArray[i].getName(), parameterArray[i].getValueStr())); } else if (parameterArray[i] instanceof IntParameter) { //if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName())) pw.println(String.format("%s=%s",parameterArray[i].getName(), parameterArray[i].getValueStr())); } else if (parameterArray[i] instanceof BoolParameter) { //if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName())) pw.println(String.format("%s=%s",parameterArray[i].getName(), parameterArray[i].getValueStr())); } else { vlog.error(String.format("Unknown parameter type for parameter %s", parameterArray[i].getName())); } } pw.flush(); pw.close(); } public static String loadViewerParameters(String filename) throws Exception { String servername = ""; String filepath; if (filename == null) { return loadFromReg(); /* String homeDir = FileUtils.getVncHomeDir(); if (homeDir == null) throw new Exception("Failed to read configuration file, "+ "can't obtain home directory path."); filepath = homeDir.concat("default.tigervnc"); */ } else { filepath = filename; } /* Read parameters from file */ File f = new File(filepath); if (!f.exists() || !f.canRead()) { if (filename == null || filename.isEmpty()) return null; throw new Exception(String.format("Failed to read configuration file, can't open %s", filepath)); } String line = ""; LineNumberReader reader; try { reader = new LineNumberReader(new FileReader(f)); } catch (FileNotFoundException e) { throw new Exception(e.getMessage()); } int lineNr = 0; while (line != null) { // Read the next line try { line = reader.readLine(); lineNr = reader.getLineNumber(); if (line == null) break; } catch (IOException e) { throw new Exception(String.format("Failed to read line %d in file %s: %s", lineNr, filepath, e.getMessage())); } // Make sure that the first line of the file has the file identifier string if(lineNr == 1) { if(line.equals(IDENTIFIER_STRING)) continue; else throw new Exception(String.format(new String("Configuration file %s is in an invalid format"), filename)); } // Skip empty lines and comments if (line.trim().isEmpty() || line.trim().startsWith("#")) continue; // Find the parameter value int idx = line.indexOf("="); if (idx == -1) { vlog.error(String.format("Failed to read line %d in file %s: %s", lineNr, filename, "Invalid format")); continue; } String value = line.substring(idx+1).trim(); boolean invalidParameterName = true; // Will be set to false below if // the line contains a valid name. if (line.substring(0,idx).trim().equalsIgnoreCase("ServerName")) { if (value.length() > 256) { vlog.error(String.format("Failed to read line %d in file %s: %s", lineNr, filepath, "Invalid format or too large value")); continue; } servername = value; invalidParameterName = false; } else { for (int i = 0; i < parameterArray.length; i++) { if (parameterArray[i] instanceof StringParameter) { if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName())) { if (value.length() > 256) { vlog.error(String.format("Failed to read line %d in file %s: %s", lineNr, filepath, "Invalid format or too large value")); continue; } ((StringParameter)parameterArray[i]).setParam(value); invalidParameterName = false; } } else if (parameterArray[i] instanceof IntParameter) { if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName())) { ((IntParameter)parameterArray[i]).setParam(value); invalidParameterName = false; } } else if (parameterArray[i] instanceof BoolParameter) { if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName())) { ((BoolParameter)parameterArray[i]).setParam(value); invalidParameterName = false; } } else { vlog.error(String.format("Unknown parameter type for parameter %s", parameterArray[i].getName())); } } } if (invalidParameterName) vlog.info(String.format("Unknown parameter %s on line %d in file %s", line, lineNr, filepath)); } try { reader.close(); } catch (IOException e) { vlog.info(e.getMessage()); } finally { try { if (reader != null) reader.close(); } catch (IOException e) { } } return servername; } public static void saveToReg(String servername) { String hKey = "global"; if (servername != null && !servername.isEmpty()) { UserPreferences.set(hKey, "ServerName", servername); updateConnHistory(servername); } for (int i = 0; i < parameterArray.length; i++) { if (parameterArray[i] instanceof StringParameter) { UserPreferences.set(hKey, parameterArray[i].getName(), parameterArray[i].getValueStr()); } else if (parameterArray[i] instanceof IntParameter) { UserPreferences.set(hKey, parameterArray[i].getName(), ((IntParameter)parameterArray[i]).getValue()); } else if (parameterArray[i] instanceof BoolParameter) { UserPreferences.set(hKey, parameterArray[i].getName(), ((BoolParameter)parameterArray[i]).getValue()); } else { vlog.error(String.format("Unknown parameter type for parameter %s", parameterArray[i].getName())); } } UserPreferences.save(hKey); } public static String loadFromReg() { String hKey = "global"; String servername = UserPreferences.get(hKey, "ServerName"); if (servername == null) servername = ""; for (int i = 0; i < parameterArray.length; i++) { if (parameterArray[i] instanceof StringParameter) { if (UserPreferences.get(hKey, parameterArray[i].getName()) != null) { String stringValue = UserPreferences.get(hKey, parameterArray[i].getName()); ((StringParameter)parameterArray[i]).setParam(stringValue); } } else if (parameterArray[i] instanceof IntParameter) { if (UserPreferences.get(hKey, parameterArray[i].getName()) != null) { int intValue = UserPreferences.getInt(hKey, parameterArray[i].getName()); ((IntParameter)parameterArray[i]).setParam(intValue); } } else if (parameterArray[i] instanceof BoolParameter) { if (UserPreferences.get(hKey, parameterArray[i].getName()) != null) { boolean booleanValue = UserPreferences.getBool(hKey, parameterArray[i].getName()); ((BoolParameter)parameterArray[i]).setParam(booleanValue); } } else { vlog.error(String.format("Unknown parameter type for parameter %s", parameterArray[i].getName())); } } return servername; } public static String loadAppletParameters(VncViewer applet) { String servername = applet.getParameter("Server"); String serverport = applet.getParameter("Port"); String embedParam = applet.getParameter("Embed"); if (servername == null) servername = applet.getCodeBase().getHost(); if (serverport != null) servername = servername.concat("::"+serverport); else servername = servername.concat("::5900"); if (embedParam != null) embed.setParam(embedParam); for (int i = 0; i < parameterArray.length; i++) { String value = applet.getParameter(parameterArray[i].getName()); if (value == null) continue; if (parameterArray[i] instanceof StringParameter) { if (value.length() > 256) { vlog.error(String.format("Failed to read applet parameter %s: %s", parameterArray[i].getName(), "Invalid format or too large value")); continue; } ((StringParameter)parameterArray[i]).setParam(value); } else if (parameterArray[i] instanceof IntParameter) { ((IntParameter)parameterArray[i]).setParam(value); } else if (parameterArray[i] instanceof BoolParameter) { ((BoolParameter)parameterArray[i]).setParam(value); } else { vlog.error(String.format("Unknown parameter type for parameter %s", parameterArray[i].getName())); } } return servername; } private static void updateConnHistory(String serverName) { String hKey = "ServerDialog"; if (serverName != null && !serverName.isEmpty()) { String valueStr = UserPreferences.get(hKey, "history"); String t = (valueStr == null) ? "" : valueStr; StringTokenizer st = new StringTokenizer(t, ","); StringBuffer sb = new StringBuffer().append(serverName); while (st.hasMoreTokens()) { String str = st.nextToken(); if (!str.equals(serverName) && !str.equals("")) sb.append(',').append(str); } UserPreferences.set(hKey, "history", sb.toString()); UserPreferences.save(hKey); } } }