Merge branch 'vncserverfix' of https://github.com/michalsrb/tigervnc
diff --git a/java/com/tigervnc/rfb/CSecurityTLS.java b/java/com/tigervnc/rfb/CSecurityTLS.java
index e7510c8..a8f6df3 100644
--- a/java/com/tigervnc/rfb/CSecurityTLS.java
+++ b/java/com/tigervnc/rfb/CSecurityTLS.java
@@ -33,6 +33,7 @@
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.InputStream;
@@ -42,6 +43,12 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+import javax.net.ssl.HostnameVerifier;
 import javax.swing.JOptionPane;
 import javax.xml.bind.DatatypeConverter;
 
@@ -60,23 +67,17 @@
 
   private void initGlobal()
   {
-    boolean globalInitDone = false;
-
-    if (!globalInitDone) {
-      try {
-        ctx = SSLContext.getInstance("TLS");
-      } catch(NoSuchAlgorithmException e) {
-        throw new Exception(e.toString());
-      }
-
-      globalInitDone = true;
+    try {
+      ctx = SSLContext.getInstance("TLS");
+    } catch(NoSuchAlgorithmException e) {
+      throw new Exception(e.toString());
     }
   }
 
   public CSecurityTLS(boolean _anon)
   {
     anon = _anon;
-    session = null;
+    manager = null;
 
     setDefaults();
     cafile = x509ca.getData();
@@ -116,7 +117,7 @@
 
     initGlobal();
 
-    if (session == null) {
+    if (manager == null) {
       if (!is.checkNoWait(1))
         return false;
 
@@ -132,21 +133,15 @@
       }
 
       setParam();
-
     }
 
     try {
       manager = new SSLEngineManager(engine, is, os);
       manager.doHandshake();
     } catch(java.lang.Exception e) {
-      if (e.getMessage().equals("X.509 certificate not trusted"))
-        throw new WarningException(e.getMessage());
-      else
-        throw new SystemException(e.toString());
+      throw new SystemException(e.toString());
     }
 
-    //checkSession();
-
     cc.setStreams(new TLSInStream(is, manager),
 		              new TLSOutStream(os, manager));
     return true;
@@ -199,13 +194,6 @@
 
   }
 
-  class MyHandshakeListener implements HandshakeCompletedListener {
-   public void handshakeCompleted(HandshakeCompletedEvent e) {
-     vlog.info("Handshake succesful!");
-     vlog.info("Using cipher suite: " + e.getCipherSuite());
-   }
-  }
-
   class MyX509TrustManager implements X509TrustManager
   {
 
@@ -263,7 +251,7 @@
         tmf.init(new CertPathTrustManagerParameters(params));
         tm = (X509TrustManager)tmf.getTrustManagers()[0];
       } catch (java.lang.Exception e) {
-        vlog.error(e.toString());
+        throw new Exception(e.getMessage());
       }
     }
 
@@ -279,8 +267,9 @@
       MessageDigest md = null;
       try {
         md = MessageDigest.getInstance("SHA-1");
+        verifyHostname(chain[0]);
         tm.checkServerTrusted(chain, authType);
-      } catch (CertificateException e) {
+      } catch (java.lang.Exception e) {
         if (e.getCause() instanceof CertPathBuilderException) {
           Object[] answer = {"YES", "NO"};
           X509Certificate cert = chain[0];
@@ -305,19 +294,19 @@
             JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE,
             null, answer, answer[0]);
           if (ret == JOptionPane.YES_OPTION) {
-            File vncDir = new File(FileUtils.getVncHomeDir());
-            if (!vncDir.exists() && !vncDir.mkdir()) {
-              vlog.info("Certificate save failed, unable to create ~/.vnc");
-              return;
-            }
             Collection<? extends X509Certificate> cacerts = null;
-            String castore =
-              FileUtils.getVncHomeDir()+"x509_savedcerts.pem";
-            File caFile = new File(castore);
+            File vncDir = new File(FileUtils.getVncHomeDir());
+            File caFile = new File(vncDir, "x509_savedcerts.pem");
             try {
-              caFile.createNewFile();
-            } catch (IOException ioe) {
-              vlog.error(ioe.getCause().getMessage());
+              if (!vncDir.exists())
+                vncDir.mkdir();
+              if (!caFile.createNewFile()) {
+                vlog.error("Certificate save failed.");
+                return;
+              }
+            } catch (java.lang.Exception ioe) {
+              // skip save if security settings prohibit access to filesystem
+              vlog.error("Certificate save failed: "+ioe.getMessage());
               return;
             }
             InputStream caStream = new MyFileInputStream(caFile);
@@ -332,30 +321,28 @@
                 pem = pem.replaceAll("(.{64})", "$1\n");
                 FileWriter fw = null;
                 try {
-                  fw = new FileWriter(castore, true);
+                  fw = new FileWriter(caFile.getAbsolutePath(), true);
                   fw.write("-----BEGIN CERTIFICATE-----\n");
                   fw.write(pem+"\n");
                   fw.write("-----END CERTIFICATE-----\n");
                 } catch (IOException ioe) {
-                  throw new Exception(ioe.getCause().getMessage());
+                  throw new Exception(ioe.getMessage());
                 } finally {
                   try {
                     if (fw != null)
                       fw.close();
                   } catch(IOException ioe2) {
-                    throw new Exception(ioe2.getCause().getMessage());
+                    throw new Exception(ioe2.getMessage());
                   }
                 }
               }
             }
           } else {
-            throw new WarningException("X.509 certificate not trusted");
+            throw new WarningException("Peer certificate verification failed.");
           }
         } else {
-          throw new SystemException(e.getCause().getMessage());
+          throw new SystemException(e.getMessage());
         }
-      } catch (java.lang.Exception e) {
-        throw new Exception(e.getCause().getMessage());
       }
     }
 
@@ -364,6 +351,53 @@
       return tm.getAcceptedIssuers();
     }
 
+    private void verifyHostname(X509Certificate cert)
+      throws CertificateParsingException
+    {
+      try {
+        Collection sans = cert.getSubjectAlternativeNames();
+        if (sans == null) {
+          String dn = cert.getSubjectX500Principal().getName();
+          LdapName ln = new LdapName(dn);
+          for (Rdn rdn : ln.getRdns()) {
+            if (rdn.getType().equalsIgnoreCase("CN")) {
+              String peer =
+                ((CConn)client).getSocket().getPeerName().toLowerCase();
+              if (peer.equals(((String)rdn.getValue()).toLowerCase()))
+                return;
+            }
+          }
+        } else {
+          Iterator i = sans.iterator();
+          while (i.hasNext()) {
+            List nxt = (List)i.next();
+            if (((Integer)nxt.get(0)).intValue() == 2) {
+              String peer =
+                ((CConn)client).getSocket().getPeerName().toLowerCase();
+              if (peer.equals(((String)nxt.get(1)).toLowerCase()))
+                return;
+            } else if (((Integer)nxt.get(0)).intValue() == 7) {
+              String peer = ((CConn)client).getSocket().getPeerAddress();
+              if (peer.equals(((String)nxt.get(1)).toLowerCase()))
+                return;
+            }
+          }
+        }
+        Object[] answer = {"YES", "NO"};
+        int ret = JOptionPane.showOptionDialog(null,
+          "Hostname verification failed. Do you want to continue?",
+          "Hostname Verification Failure",
+          JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE,
+          null, answer, answer[0]);
+        if (ret != JOptionPane.YES_OPTION)
+          throw new WarningException("Hostname verification failed.");
+      } catch (CertificateParsingException e) {
+        throw new SystemException(e.getMessage());
+      } catch (InvalidNameException e) {
+        throw new SystemException(e.getMessage());
+      }
+    }
+
     private class MyFileInputStream extends InputStream {
       // Blank lines in a certificate file will cause Java 6 to throw a
       // "DerInputStream.getLength(): lengthTag=127, too big" exception.
@@ -390,7 +424,7 @@
             if (reader != null)
               reader.close();
           } catch(IOException ioe) {
-            throw new Exception(ioe.getCause().getMessage());
+            throw new Exception(ioe.getMessage());
           }
         }
         Charset utf8 = Charset.forName("UTF-8");
@@ -412,7 +446,6 @@
         return len;
       }
 
-
       @Override
       public int read() throws IOException {
         if (!buf.hasRemaining())
@@ -426,13 +459,9 @@
   public final String description()
     { return anon ? "TLS Encryption without VncAuth" : "X509 Encryption without VncAuth"; }
 
-  //protected void checkSession();
   protected CConnection client;
 
-
-
   private SSLContext ctx;
-  private SSLSession session;
   private SSLEngine engine;
   private SSLEngineManager manager;
   private boolean anon;
diff --git a/java/com/tigervnc/vncviewer/CConn.java b/java/com/tigervnc/vncviewer/CConn.java
index 88b25ec..f592cb5 100644
--- a/java/com/tigervnc/vncviewer/CConn.java
+++ b/java/com/tigervnc/vncviewer/CConn.java
@@ -1411,6 +1411,10 @@
 
   public void actionPerformed(ActionEvent e) {}
 
+  public Socket getSocket() {
+    return sock;
+  }
+
   ////////////////////////////////////////////////////////////////////
   // The following methods are called from both RFB and GUI threads
 
diff --git a/java/com/tigervnc/vncviewer/VncViewer.java b/java/com/tigervnc/vncviewer/VncViewer.java
index 0f7ce8e..0c54d79 100644
--- a/java/com/tigervnc/vncviewer/VncViewer.java
+++ b/java/com/tigervnc/vncviewer/VncViewer.java
@@ -464,19 +464,18 @@
       cc = new CConn(this, sock, vncServerName.getValue());
       while (!cc.shuttingDown)
         cc.processMsg();
+      exit(0);
     } catch (java.lang.Exception e) {
       if (cc == null || !cc.shuttingDown) {
         reportException(e);
         if (cc != null)
           cc.deleteWindow();
-        exit(1);
       } else if (embed.getValue()) {
         reportException(new java.lang.Exception("Connection closed"));
-      } else {
-        cc = null;
+        exit(0);
       }
+      exit(1);
     }
-    exit(0);
   }
 
   static BoolParameter noLionFS