// Copyright (C) 2002-2005 Ultr@VNC Team. All Rights Reserved. // Copyright (C) 2004 Kenn Min Chong, John Witchel. All Rights Reserved. // Copyright (C) 2004 Alban Chazot. All Rights Reserved. // Copyright (C) 2001,2002 HorizonLive.com, Inc. All Rights Reserved. // Copyright (C) 2002 Constantin Kaplinsky. All Rights Reserved. // Copyright (C) 1999 AT&T Laboratories Cambridge. 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. // // // VncViewer.java - the VNC viewer applet. This class mainly just sets up the // user interface, leaving it to the VncCanvas to do the actual rendering of // a VNC desktop. // // Alban Chazot - Carmi Grenoble July 5th 2004 // * Add support for Ultr@VNC mslogon feature. // You can now be connected to a Ultr@VNC box with mslogon required. // Thanks to Wim Vandersmissen, who provide a TightVNC viewer patch do to so. // That give me the idea to provide it in the java viewer too. // // * Add ScrollPanel to applet mode // import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; import javax.swing.*; public class VncViewer extends java.applet.Applet implements java.lang.Runnable, WindowListener { boolean inAnApplet = true; boolean inSeparateFrame = false; // mslogon support boolean mslogon = false; // mslogon support end // // main() is called when run as a java program from the command line. // It simply runs the applet inside a newly-created frame. // public static void main(String[] argv) { VncViewer v = new VncViewer(); v.mainArgs = argv; v.inAnApplet = false; v.inSeparateFrame = true; v.init(); v.start(); } String[] mainArgs; RfbProto rfb; Thread rfbThread; Frame vncFrame; Container vncContainer; ScrollPane desktopScrollPane; GridBagLayout gridbag; ButtonPanel buttonPanel; //AuthPanel authenticator; VncCanvas vc; OptionsFrame options; ClipboardFrame clipboard; RecordingFrame rec; FTPFrame ftp; // KMC: FTP Frame declaration // Control session recording. Object recordingSync; String sessionFileName; boolean recordingActive; boolean recordingStatusChanged; String cursorUpdatesDef; String eightBitColorsDef; // Variables read from parameter values. String host; int port; String passwordParam; String encPasswordParam; boolean showControls; boolean showOfflineDesktop; int deferScreenUpdates; int deferCursorUpdates; int deferUpdateRequests; String serverID; // mslogon support 2 String usernameParam; String encUsernameParam; String dm; byte[] domain = new byte[256]; byte[] user = new byte[256]; byte[] passwd = new byte[32]; int i; // mslogon support 2 end // // init() // public void init() { readParameters(); if (inSeparateFrame) { vncFrame = new Frame("Ultr@VNC"); if (!inAnApplet) { vncFrame.add("Center", this); } vncContainer = vncFrame; } else { vncContainer = this; } recordingSync = new Object(); options = new OptionsFrame(this); clipboard = new ClipboardFrame(this); // authenticator = new AuthPanel(false); // mslogon support : go to connectAndAuthenticate() if (RecordingFrame.checkSecurity()) rec = new RecordingFrame(this); sessionFileName = null; recordingActive = false; recordingStatusChanged = false; cursorUpdatesDef = null; eightBitColorsDef = null; if (inSeparateFrame) vncFrame.addWindowListener(this); ftp = new FTPFrame(this); // KMC: FTPFrame creation rfbThread = new Thread(this); rfbThread.start(); } public void update(Graphics g) { } // // run() - executed by the rfbThread to deal with the RFB socket. // public void run() { gridbag = new GridBagLayout(); vncContainer.setLayout(gridbag); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.anchor = GridBagConstraints.NORTHWEST; // modif appshare pvandermaesen@noctis.be, never show control /* if (showControls) { buttonPanel = new ButtonPanel(this); gridbag.setConstraints(buttonPanel, gbc); vncContainer.add(buttonPanel); } */ try { connectAndAuthenticate(); doProtocolInitialisation(); vc = new VncCanvas(this); gbc.weightx = 1.0; gbc.weighty = 1.0; // Add ScrollPanel to applet mode // Create a panel which itself is resizeable and can hold // non-resizeable VncCanvas component at the top left corner. Panel canvasPanel = new Panel(); canvasPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); canvasPanel.add(vc); // Create a ScrollPane which will hold a panel with VncCanvas // inside. desktopScrollPane = new ScrollPane(ScrollPane.SCROLLBARS_AS_NEEDED); gbc.fill = GridBagConstraints.BOTH; gridbag.setConstraints(desktopScrollPane, gbc); desktopScrollPane.add(canvasPanel); if (inSeparateFrame) { // Finally, add our ScrollPane to the Frame window. vncFrame.add(desktopScrollPane); vncFrame.setTitle(rfb.desktopName); vncFrame.pack(); vc.resizeDesktopFrame(); } else { // Finally, add the scrollable panel component to the Applet. gridbag.setConstraints(desktopScrollPane, gbc); add(desktopScrollPane); // Add ScrollPanel to applet mode end validate(); } moveFocusToDesktop(); vc.processNormalProtocol(); } catch (NoRouteToHostException e) { e.printStackTrace(); fatalError("Network error: no route to server: " + host); } catch (UnknownHostException e) { e.printStackTrace(); fatalError("Network error: server name unknown: " + host); } catch (ConnectException e) { e.printStackTrace(); fatalError("Network error: could not connect to server: " + host + ":" + port); } catch (EOFException e) { e.printStackTrace(); if (showOfflineDesktop) { System.out.println("Network error: remote side closed connection"); if (vc != null) { vc.enableInput(false); } if (inSeparateFrame) { vncFrame.setTitle(rfb.desktopName + " [disconnected]"); } if (rfb != null) { rfb.close(); rfb = null; } if (showControls && buttonPanel != null) { buttonPanel.disableButtonsOnDisconnect(); if (inSeparateFrame) { vncFrame.pack(); } else { validate(); } } } else { fatalError("Network error: remote side closed connection"); } } catch (IOException e) { String str = e.getMessage(); e.printStackTrace(); if (str != null && str.length() != 0) { fatalError("Network Error: " + str); } else { fatalError(e.toString()); } } catch (Exception e) { String str = e.getMessage(); e.printStackTrace(); if (str != null && str.length() != 0) { fatalError("Error: " + str); } else { fatalError(e.toString()); } } } // // Connect to the RFB server and authenticate the user. // void connectAndAuthenticate() throws Exception { // If "ENCPASSWORD" parameter is set, decrypt the password into // the passwordParam string. if (encPasswordParam != null) { // ENCPASSWORD is hexascii-encoded. Decode. byte[] pw = { 0, 0, 0, 0, 0, 0, 0, 0 }; int len = encPasswordParam.length() / 2; if (len > 8) len = 8; for (int i = 0; i < len; i++) { String hex = encPasswordParam.substring(i * 2, i * 2 + 2); Integer x = new Integer(Integer.parseInt(hex, 16)); pw[i] = x.byteValue(); } // Decrypt the password. byte[] key = { 23, 82, 107, 6, 35, 78, 88, 7 }; DesCipher des = new DesCipher(key); des.decrypt(pw, 0, pw, 0); passwordParam = new String(pw); } // If a password parameter ("PASSWORD" or "ENCPASSWORD") is set, // don't ask user a password, get one from passwordParam instead. // Authentication failures would be fatal. if (passwordParam != null) { if (inSeparateFrame) { vncFrame.pack(); vncFrame.show(); } else { validate(); } /* modif for autologin for dokeos appshare (noctis) */ /* end modif */ if (!tryAuthenticate(usernameParam, passwordParam)) { throw new Exception("VNC authentication failed"); } return; } // There is no "PASSWORD" or "ENCPASSWORD" parameters -- ask user // for a password, try to authenticate, retry on authentication // failures. // mslogon support // // Detect Auth Protocol (Ultr@VNC or the standard One) // To know if we must show the username box // prologueDetectAuthProtocol(); //authenticator = new AuthPanel(mslogon); // mslogon support end GridBagConstraints gbc = new GridBagConstraints(); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.anchor = GridBagConstraints.NORTHWEST; gbc.weightx = 1.0; gbc.weighty = 1.0; gbc.ipadx = 100; gbc.ipady = 50; //gridbag.setConstraints(authenticator, gbc); //vncContainer.add(authenticator); if (inSeparateFrame) { vncFrame.pack(); vncFrame.show(); } else { validate(); // FIXME: here moveFocusToPasswordField() does not always work // under Netscape 4.7x/Java 1.1.5/Linux. It seems like this call // is being executed before the password field of the // authenticator is fully drawn and activated, therefore // requestFocus() does not work. Currently, I don't know how to // solve this problem. // -- const // mslogon support //authenticator.moveFocusToUsernameField(); // mslogon support end } while (true) { // Wait for user entering a password, or a username and a password // Try to authenticate with a given password. // mslogon support String us=""; if (mslogon) { //us = authenticator.username.getText(); } else { us = ""; } /* add for dokeos appShare (noctis) */ if (tryAuthenticate(us, "dokeos")) break; } } // mslogon support // // Detect Server rfb Protocol to know the auth Method // Perform a connexion to detect the Serverminor // void prologueDetectAuthProtocol() throws Exception { rfb = new RfbProto(host, port, this, serverID); rfb.readVersionMsg(); System.out.println("RFB server supports protocol version " + rfb.serverMajor + "." + rfb.serverMinor); // Mslogon support if (rfb.serverMinor == 4) { mslogon = true; System.out.println("Ultr@VNC mslogon detected"); } rfb.writeVersionMsg(); } // mslogon support end // // Try to authenticate with a given password. // boolean tryAuthenticate(String us, String pw) throws Exception { rfb = new RfbProto(host, port, this, serverID); rfb.readVersionMsg(); System.out.println("RFB server supports protocol version " + rfb.serverMajor + "." + rfb.serverMinor); rfb.writeVersionMsg(); int authScheme = rfb.readAuthScheme(); switch (authScheme) { case RfbProto.NoAuth: System.out.println("No authentication needed"); return true; case RfbProto.VncAuth: if (mslogon) { System.out.println("showing JOptionPane warning."); int n = JOptionPane.showConfirmDialog(vncFrame, "The current authentication method does not transfer your password securely." + "Do you want to continue?", "Warning", JOptionPane.YES_NO_OPTION); if (n != JOptionPane.YES_OPTION) { throw new Exception("User cancelled insecure MS-Logon"); } } // mslogon support byte[] challengems = new byte[64]; if (mslogon) { // copy the us (user) parameter into the user Byte formated variable System.arraycopy(us.getBytes(), 0, user, 0, us.length()); // and pad it with Null if (us.length() < 256) { for (i = us.length(); i < 256; i++) { user[i] = 0; } } dm = "."; // copy the dm (domain) parameter into the domain Byte formated variable System.arraycopy(dm.getBytes(), 0, domain, 0, dm.length()); // and pad it with Null if (dm.length() < 256) { for (i = dm.length(); i < 256; i++) { domain[i] = 0; } } // equivalent of vncEncryptPasswdMS // copy the pw (password) parameter into the password Byte formated variable System.arraycopy(pw.getBytes(), 0, passwd, 0, pw.length()); // and pad it with Null if (pw.length() < 32) { for (i = pw.length(); i < 32; i++) { passwd[i] = 0; } } // Encrypt the full given password byte[] fixedkey = { 23, 82, 107, 6, 35, 78, 88, 7 }; DesCipher desme = new DesCipher(fixedkey); desme.encrypt(passwd, 0, passwd, 0); // end equivalent of vncEncryptPasswdMS // get the mslogon Challenge from server rfb.is.readFully(challengems); } // mslogon support end byte[] challenge = new byte[16]; rfb.is.readFully(challenge); if (pw.length() > 8) pw = pw.substring(0, 8); // Truncate to 8 chars // vncEncryptBytes in the UNIX libvncauth truncates password // after the first zero byte. We do to. int firstZero = pw.indexOf(0); if (firstZero != -1) pw = pw.substring(0, firstZero); // mslogon support if (mslogon) { for (i = 0; i < 32; i++) { challengems[i] = (byte) (passwd[i] ^ challengems[i]); } rfb.os.write(user); rfb.os.write(domain); rfb.os.write(challengems); } // mslogon support end byte[] key = { 0, 0, 0, 0, 0, 0, 0, 0 }; System.arraycopy(pw.getBytes(), 0, key, 0, pw.length()); DesCipher des = new DesCipher(key); des.encrypt(challenge, 0, challenge, 0); des.encrypt(challenge, 8, challenge, 8); rfb.os.write(challenge); int authResult = rfb.is.readInt(); switch (authResult) { case RfbProto.VncAuthOK: System.out.println("VNC authentication succeeded"); return true; case RfbProto.VncAuthFailed: System.out.println("VNC authentication failed"); break; case RfbProto.VncAuthTooMany: throw new Exception("VNC authentication failed - too many tries"); default: throw new Exception("Unknown VNC authentication result " + authResult); } break; case RfbProto.MsLogon: System.out.println("MS-Logon (DH) detected"); if (AuthMsLogon(us, pw)) { return true; } break; default: throw new Exception("Unknown VNC authentication scheme " + authScheme); } return false; } // marscha@2006: Try to better hide the windows password. // I know that this is no breakthrough in modern cryptography. // It's just a patch/kludge/workaround. boolean AuthMsLogon(String us, String pw) throws Exception { byte user[] = new byte[256]; byte passwd[] = new byte[64]; long gen = rfb.is.readLong(); long mod = rfb.is.readLong(); long resp = rfb.is.readLong(); DH dh = new DH(gen, mod); long pub = dh.createInterKey(); rfb.os.write(DH.longToBytes(pub)); long key = dh.createEncryptionKey(resp); System.out.println("gen=" + gen + ", mod=" + mod + ", pub=" + pub + ", key=" + key); DesCipher des = new DesCipher(DH.longToBytes(key)); System.arraycopy(us.getBytes(), 0, user, 0, us.length()); // and pad it with Null if (us.length() < 256) { for (i = us.length(); i < 256; i++) { user[i] = 0; } } // copy the pw (password) parameter into the password Byte formated variable System.arraycopy(pw.getBytes(), 0, passwd, 0, pw.length()); // and pad it with Null if (pw.length() < 32) { for (i = pw.length(); i < 32; i++) { passwd[i] = 0; } } // user = domain + "\\" + user; des.encryptText(user, user, DH.longToBytes(key)); des.encryptText(passwd, passwd, DH.longToBytes(key)); rfb.os.write(user); rfb.os.write(passwd); int authResult = rfb.is.readInt(); switch (authResult) { case RfbProto.VncAuthOK: System.out.println("MS-Logon (DH) authentication succeeded"); return true; case RfbProto.VncAuthFailed: System.out.println("MS-Logon (DH) authentication failed"); break; case RfbProto.VncAuthTooMany: throw new Exception("MS-Logon (DH) authentication failed - too many tries"); default: throw new Exception("Unknown MS-Logon (DH) authentication result " + authResult); } return false; } // // Do the rest of the protocol initialisation. // void doProtocolInitialisation() throws IOException { rfb.writeClientInit(); rfb.readServerInit(); System.out.println("Desktop name is " + rfb.desktopName); System.out.println("Desktop size is " + rfb.framebufferWidth + " x " + rfb.framebufferHeight); setEncodings(); } // // Send current encoding list to the RFB server. // void setEncodings() { try { if (rfb != null && rfb.inNormalProtocol) { rfb.writeSetEncodings(options.encodings, options.nEncodings); if (vc != null) { vc.softCursorFree(); } } } catch (Exception e) { e.printStackTrace(); } } // // setCutText() - send the given cut text to the RFB server. // void setCutText(String text) { try { if (rfb != null && rfb.inNormalProtocol) { rfb.writeClientCutText(text); } } catch (Exception e) { e.printStackTrace(); } } // // Order change in session recording status. To stop recording, pass // null in place of the fname argument. // void setRecordingStatus(String fname) { synchronized (recordingSync) { sessionFileName = fname; recordingStatusChanged = true; } } // // Start or stop session recording. Returns true if this method call // causes recording of a new session. // boolean checkRecordingStatus() throws IOException { synchronized (recordingSync) { if (recordingStatusChanged) { recordingStatusChanged = false; if (sessionFileName != null) { startRecording(); return true; } else { stopRecording(); } } } return false; } // // Start session recording. // protected void startRecording() throws IOException { synchronized (recordingSync) { if (!recordingActive) { // Save settings to restore them after recording the session. cursorUpdatesDef = options.choices[options.cursorUpdatesIndex].getSelectedItem(); eightBitColorsDef = options.choices[options.eightBitColorsIndex].getSelectedItem(); // Set options to values suitable for recording. options.choices[options.cursorUpdatesIndex].select("Disable"); options.choices[options.cursorUpdatesIndex].setEnabled(false); options.setEncodings(); options.choices[options.eightBitColorsIndex].select("Full"); options.choices[options.eightBitColorsIndex].setEnabled(false); options.setColorFormat(); } else { rfb.closeSession(); } System.out.println("Recording the session in " + sessionFileName); rfb.startSession(sessionFileName); recordingActive = true; } } // // Stop session recording. // protected void stopRecording() throws IOException { synchronized (recordingSync) { if (recordingActive) { // Restore options. options.choices[options.cursorUpdatesIndex].select(cursorUpdatesDef); options.choices[options.cursorUpdatesIndex].setEnabled(true); options.setEncodings(); options.choices[options.eightBitColorsIndex].select(eightBitColorsDef); options.choices[options.eightBitColorsIndex].setEnabled(true); options.setColorFormat(); rfb.closeSession(); System.out.println("Session recording stopped."); } sessionFileName = null; recordingActive = false; } } // // readParameters() - read parameters from the html source or from the // command line. On the command line, the arguments are just a sequence of // param_name/param_value pairs where the names and values correspond to // those expected in the html applet tag source. // public void readParameters() { host = readParameter("HOST", !inAnApplet); if (host == null) { host = getCodeBase().getHost(); if (host.equals("")) { fatalError("HOST parameter not specified"); } } serverID = readParameter("SERVERID", true); String str = readParameter("PORT", true); port = Integer.parseInt(str); if (inAnApplet) { str = readParameter("Open New Window", false); if (str != null && str.equalsIgnoreCase("Yes")) inSeparateFrame = true; } encPasswordParam = readParameter("ENCPASSWORD", false); if (encPasswordParam == null) passwordParam = readParameter("PASSWORD", false); // "Show Controls" set to "No" disables button panel. showControls = true; str = readParameter("Show Controls", false); if (str != null && str.equalsIgnoreCase("No")) showControls = false; // Do we continue showing desktop on remote disconnect? showOfflineDesktop = false; str = readParameter("Show Offline Desktop", false); if (str != null && str.equalsIgnoreCase("Yes")) showOfflineDesktop = true; // Fine tuning options. deferScreenUpdates = readIntParameter("Defer screen updates", 20); deferCursorUpdates = readIntParameter("Defer cursor updates", 10); deferUpdateRequests = readIntParameter("Defer update requests", 50); } public String readParameter(String name, boolean required) { if (inAnApplet) { String s = getParameter(name); if ((s == null) && required) { fatalError(name + " parameter not specified"); } return s; } for (int i = 0; i < mainArgs.length; i += 2) { if (mainArgs[i].equalsIgnoreCase(name)) { try { return mainArgs[i + 1]; } catch (Exception e) { if (required) { fatalError(name + " parameter not specified"); } return null; } } } if (required) { fatalError(name + " parameter not specified"); } return null; } int readIntParameter(String name, int defaultValue) { String str = readParameter(name, false); int result = defaultValue; if (str != null) { try { result = Integer.parseInt(str); } catch (NumberFormatException e) { } } return result; } // // moveFocusToDesktop() - move keyboard focus either to the // VncCanvas or to the AuthPanel. // void moveFocusToDesktop() { if (vncContainer != null) { if (vc != null && vncContainer.isAncestorOf(vc)) { vc.requestFocus(); } } } // // disconnect() - close connection to server. // boolean disconnectRequested = false; synchronized public void disconnect() { disconnectRequested = true; if (rfb != null) { rfb.close(); rfb = null; } System.out.println("Disconnect"); options.dispose(); clipboard.dispose(); if (rec != null) rec.dispose(); if (inAnApplet) { vncContainer.removeAll(); Label errLabel = new Label("Disconnected"); errLabel.setFont(new Font("Helvetica", Font.PLAIN, 12)); vncContainer.setLayout(new FlowLayout(FlowLayout.LEFT, 30, 30)); vncContainer.add(errLabel); if (inSeparateFrame) { vncFrame.pack(); } else { validate(); } rfbThread.stop(); } else { System.exit(0); } } // // fatalError() - print out a fatal error message. // synchronized public void fatalError(String str) { if (rfb != null) { rfb.close(); rfb = null; } System.out.println(str); if (disconnectRequested) { // Not necessary to show error message if the error was caused // by I/O problems after the disconnect() method call. disconnectRequested = false; return; } if (inAnApplet) { vncContainer.removeAll(); Label errLabel = new Label(str); errLabel.setFont(new Font("Helvetica", Font.PLAIN, 12)); vncContainer.setLayout(new FlowLayout(FlowLayout.LEFT, 30, 30)); vncContainer.add(errLabel); if (inSeparateFrame) { vncFrame.pack(); } else { validate(); } Thread.currentThread().stop(); } else { System.exit(1); } } // // This method is called before the applet is destroyed. // public void destroy() { vncContainer.removeAll(); options.dispose(); clipboard.dispose(); if (ftp != null) ftp.dispose(); if (rec != null) rec.dispose(); if (rfb != null) rfb.close(); if (inSeparateFrame) vncFrame.dispose(); } // // Close application properly on window close event. // public void windowClosing(WindowEvent evt) { if (rfb != null) disconnect(); vncFrame.dispose(); if (!inAnApplet) { System.exit(0); } } // // Move the keyboard focus to the password field on window activation. // public void windowActivated(WindowEvent evt) { } // // Ignore window events we're not interested in. // public void windowDeactivated(WindowEvent evt) { } public void windowOpened(WindowEvent evt) { } public void windowClosed(WindowEvent evt) { } public void windowIconified(WindowEvent evt) { } public void windowDeiconified(WindowEvent evt) { } }