PageRenderTime 52ms CodeModel.GetById 14ms app.highlight 31ms RepoModel.GetById 1ms app.codeStats 1ms

/platform/com.subgraph.sgmail.javamail/src/com/sun/mail/util/SocketFetcher.java

https://github.com/subgraph/sgmail
Java | 726 lines | 442 code | 49 blank | 235 comment | 112 complexity | 3ac9a73219653b37380d54a2b1bb5473 MD5 | raw file
  1/*
  2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  3 *
  4 * Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
  5 *
  6 * The contents of this file are subject to the terms of either the GNU
  7 * General Public License Version 2 only ("GPL") or the Common Development
  8 * and Distribution License("CDDL") (collectively, the "License").  You
  9 * may not use this file except in compliance with the License.  You can
 10 * obtain a copy of the License at
 11 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 12 * or packager/legal/LICENSE.txt.  See the License for the specific
 13 * language governing permissions and limitations under the License.
 14 *
 15 * When distributing the software, include this License Header Notice in each
 16 * file and include the License file at packager/legal/LICENSE.txt.
 17 *
 18 * GPL Classpath Exception:
 19 * Oracle designates this particular file as subject to the "Classpath"
 20 * exception as provided by Oracle in the GPL Version 2 section of the License
 21 * file that accompanied this code.
 22 *
 23 * Modifications:
 24 * If applicable, add the following below the License Header, with the fields
 25 * enclosed by brackets [] replaced by your own identifying information:
 26 * "Portions Copyright [year] [name of copyright owner]"
 27 *
 28 * Contributor(s):
 29 * If you wish your version of this file to be governed by only the CDDL or
 30 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 31 * elects to include this software in this distribution under the [CDDL or GPL
 32 * Version 2] license."  If you don't indicate a single choice of license, a
 33 * recipient has the option to distribute your version of this file under
 34 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 35 * its licensees as provided above.  However, if you add GPL Version 2 code
 36 * and therefore, elected the GPL Version 2 license, then the option applies
 37 * only if the new code is made subject to such option by the copyright
 38 * holder.
 39 */
 40
 41package com.sun.mail.util;
 42
 43import java.security.*;
 44import java.net.*;
 45import java.io.*;
 46import java.lang.reflect.*;
 47import java.util.*;
 48import java.util.regex.*;
 49import java.util.logging.Level;
 50import java.security.cert.*;
 51import javax.net.*;
 52import javax.net.ssl.*;
 53import javax.security.auth.x500.X500Principal;
 54
 55/**
 56 * This class is used to get Sockets. Depending on the arguments passed
 57 * it will either return a plain java.net.Socket or dynamically load
 58 * the SocketFactory class specified in the classname param and return
 59 * a socket created by that SocketFactory.
 60 *
 61 * @author Max Spivak
 62 * @author Bill Shannon
 63 */
 64public class SocketFetcher {
 65
 66    private static MailLogger logger = new MailLogger(
 67	SocketFetcher.class,
 68	"socket",
 69	"DEBUG SocketFetcher",
 70	PropUtil.getBooleanSystemProperty("mail.socket.debug", false),
 71	System.out);
 72
 73    // No one should instantiate this class.
 74    private SocketFetcher() {
 75    }
 76
 77    /**
 78     * This method returns a Socket.  Properties control the use of
 79     * socket factories and other socket characteristics.  The properties
 80     * used are: <p>
 81     * <ul>
 82     * <li> <i>prefix</i>.socketFactory
 83     * <li> <i>prefix</i>.socketFactory.class
 84     * <li> <i>prefix</i>.socketFactory.fallback
 85     * <li> <i>prefix</i>.socketFactory.port
 86     * <li> <i>prefix</i>.ssl.socketFactory
 87     * <li> <i>prefix</i>.ssl.socketFactory.class
 88     * <li> <i>prefix</i>.ssl.socketFactory.port
 89     * <li> <i>prefix</i>.timeout
 90     * <li> <i>prefix</i>.connectiontimeout
 91     * <li> <i>prefix</i>.localaddress
 92     * <li> <i>prefix</i>.localport
 93     * </ul> <p>
 94     * If we're making an SSL connection, the ssl.socketFactory
 95     * properties are used first, if set. <p>
 96     *
 97     * If the socketFactory property is set, the value is an
 98     * instance of a SocketFactory class, not a string.  The
 99     * instance is used directly.  If the socketFactory property
100     * is not set, the socketFactory.class property is considered.
101     * (Note that the SocketFactory property must be set using the
102     * <code>put</code> method, not the <code>setProperty</code>
103     * method.) <p>
104     *
105     * If the socketFactory.class property isn't set, the socket
106     * returned is an instance of java.net.Socket connected to the
107     * given host and port. If the socketFactory.class property is set,
108     * it is expected to contain a fully qualified classname of a
109     * javax.net.SocketFactory subclass.  In this case, the class is
110     * dynamically instantiated and a socket created by that
111     * SocketFactory is returned. <p>
112     *
113     * If the socketFactory.fallback property is set to false, don't
114     * fall back to using regular sockets if the socket factory fails. <p>
115     *
116     * The socketFactory.port specifies a port to use when connecting
117     * through the socket factory.  If unset, the port argument will be
118     * used.  <p>
119     *
120     * If the connectiontimeout property is set, the timeout is passed
121     * to the socket connect method. <p>
122     *
123     * If the timeout property is set, it is used to set the socket timeout.
124     * <p>
125     *
126     * If the localaddress property is set, it's used as the local address
127     * to bind to.  If the localport property is also set, it's used as the
128     * local port number to bind to.
129     *
130     * @param host The host to connect to
131     * @param port The port to connect to at the host
132     * @param props Properties object containing socket properties
133     * @param prefix Property name prefix, e.g., "mail.imap"
134     * @param useSSL use the SSL socket factory as the default
135     */
136    public static Socket getSocket(String host, int port, Properties props,
137				String prefix, boolean useSSL)
138				throws IOException {
139
140	if (logger.isLoggable(Level.FINER))
141	    logger.finer("getSocket" + ", host " + host + ", port " + port +
142				", prefix " + prefix + ", useSSL " + useSSL);
143	if (prefix == null)
144	    prefix = "socket";
145	if (props == null)
146	    props = new Properties();	// empty
147	int cto = PropUtil.getIntProperty(props,
148					prefix + ".connectiontimeout", -1);
149	Socket socket = null;
150	String localaddrstr = props.getProperty(prefix + ".localaddress", null);
151	InetAddress localaddr = null;
152	if (localaddrstr != null)
153	    localaddr = InetAddress.getByName(localaddrstr);
154	int localport = PropUtil.getIntProperty(props,
155					prefix + ".localport", 0);
156
157	boolean fb = PropUtil.getBooleanProperty(props,
158				prefix + ".socketFactory.fallback", true);
159
160	int sfPort = -1;
161	String sfErr = "unknown socket factory";
162	int to = PropUtil.getIntProperty(props, prefix + ".timeout", -1);
163	try {
164	    /*
165	     * If using SSL, first look for SSL-specific class name or
166	     * factory instance.
167	     */
168	    SocketFactory sf = null;
169	    String sfPortName = null;
170	    if (useSSL) {
171		Object sfo = props.get(prefix + ".ssl.socketFactory");
172		if (sfo instanceof SocketFactory) {
173		    sf = (SocketFactory)sfo;
174		    sfErr = "SSL socket factory instance " + sf;
175		}
176		if (sf == null) {
177		    String sfClass =
178			props.getProperty(prefix + ".ssl.socketFactory.class");
179		    sf = getSocketFactory(sfClass);
180		    sfErr = "SSL socket factory class " + sfClass;
181		}
182		sfPortName = ".ssl.socketFactory.port";
183	    }
184
185	    if (sf == null) {
186		Object sfo = props.get(prefix + ".socketFactory");
187		if (sfo instanceof SocketFactory) {
188		    sf = (SocketFactory)sfo;
189		    sfErr = "socket factory instance " + sf;
190		}
191		if (sf == null) {
192		    String sfClass =
193			props.getProperty(prefix + ".socketFactory.class");
194		    sf = getSocketFactory(sfClass);
195		    sfErr = "socket factory class " + sfClass;
196		}
197		sfPortName = ".socketFactory.port";
198	    }
199
200	    // if we now have a socket factory, use it
201	    if (sf != null) {
202		sfPort = PropUtil.getIntProperty(props,
203						prefix + sfPortName, -1);
204
205		// if port passed in via property isn't valid, use param
206		if (sfPort == -1)
207		    sfPort = port;
208		socket = createSocket(localaddr, localport,
209		    host, sfPort, cto, to, props, prefix, sf, useSSL);
210	    }
211	} catch (SocketTimeoutException sex) {
212	    throw sex;
213	} catch (Exception ex) {
214	    if (!fb) {
215		if (ex instanceof InvocationTargetException) {
216		    Throwable t =
217		      ((InvocationTargetException)ex).getTargetException();
218		    if (t instanceof Exception)
219			ex = (Exception)t;
220		}
221		if (ex instanceof IOException)
222		    throw (IOException)ex;
223		throw new SocketConnectException("Using " + sfErr, ex,
224						host, sfPort, cto);
225	    }
226	}
227
228	if (socket == null) {
229	    socket = createSocket(localaddr, localport,
230		    host, port, cto, to, props, prefix, null, useSSL);
231
232	} else {
233	    if (to >= 0)
234		socket.setSoTimeout(to);
235	}
236
237	return socket;
238    }
239
240    public static Socket getSocket(String host, int port, Properties props,
241				String prefix) throws IOException {
242	return getSocket(host, port, props, prefix, false);
243    }
244
245    /**
246     * Create a socket with the given local address and connected to
247     * the given host and port.  Use the specified connection timeout
248     * and read timeout.
249     * If a socket factory is specified, use it.  Otherwise, use the
250     * SSLSocketFactory if useSSL is true.
251     */
252    private static Socket createSocket(InetAddress localaddr, int localport,
253				String host, int port, int cto, int to,
254				Properties props, String prefix,
255				SocketFactory sf, boolean useSSL)
256				throws IOException {
257	Socket socket = null;
258
259	String socksHost = props.getProperty(prefix + ".socks.host", null);
260	int socksPort = 1080;
261	String err = null;
262	if (socksHost != null) {
263	    int i = socksHost.indexOf(':');
264	    if (i >= 0) {
265		socksHost = socksHost.substring(0, i);
266		try {
267		    socksPort = Integer.parseInt(socksHost.substring(i + 1));
268		} catch (NumberFormatException ex) {
269		    // ignore it
270		}
271	    }
272	    socksPort = PropUtil.getIntProperty(props,
273					prefix + ".socks.port", socksPort);
274	    err = "Using SOCKS host, port: " + socksHost + ", " + socksPort;
275	    if (logger.isLoggable(Level.FINER))
276		logger.finer("socks host " + socksHost + ", port " + socksPort);
277	}
278
279	if (sf != null)
280	    socket = sf.createSocket();
281	if (socket == null) {
282	    if (socksHost != null)
283		socket = new Socket(
284				new java.net.Proxy(java.net.Proxy.Type.SOCKS,
285				new InetSocketAddress(socksHost, socksPort)));
286	    else
287		socket = new Socket();
288	}
289	if (to >= 0)
290	    socket.setSoTimeout(to);
291	int writeTimeout = PropUtil.getIntProperty(props,
292						prefix + ".writetimeout", -1);
293	if (writeTimeout != -1)	// wrap original
294	    socket = new WriteTimeoutSocket(socket, writeTimeout);
295	if (localaddr != null)
296	    socket.bind(new InetSocketAddress(localaddr, localport));
297	try {
298	    if (cto >= 0)
299		socket.connect(new InetSocketAddress(host, port), cto);
300	    else
301		socket.connect(new InetSocketAddress(host, port));
302	} catch (IOException ex) {
303	    throw new SocketConnectException(err, ex, host, port, cto);
304	}
305
306	/*
307	 * If we want an SSL connection and we didn't get an SSLSocket,
308	 * wrap our plain Socket with an SSLSocket.
309	 */
310	if (useSSL && !(socket instanceof SSLSocket)) {
311	    String trusted;
312	    SSLSocketFactory ssf;
313	    if ((trusted = props.getProperty(prefix + ".ssl.trust")) != null) {
314		try {
315		    MailSSLSocketFactory msf = new MailSSLSocketFactory();
316		    if (trusted.equals("*"))
317			msf.setTrustAllHosts(true);
318		    else
319			msf.setTrustedHosts(trusted.split("\\s+"));
320		    ssf = msf;
321		} catch (GeneralSecurityException gex) {
322		    IOException ioex = new IOException(
323				    "Can't create MailSSLSocketFactory");
324		    ioex.initCause(gex);
325		    throw ioex;
326		}
327	    } else
328		ssf = (SSLSocketFactory)SSLSocketFactory.getDefault();
329	    socket = ssf.createSocket(socket, host, port, true);
330	    sf = ssf;
331	}
332
333	/*
334	 * No matter how we created the socket, if it turns out to be an
335	 * SSLSocket, configure it.
336	 */
337	configureSSLSocket(socket, host, props, prefix, sf);
338
339	return socket;
340    }
341
342    /**
343     * Return a socket factory of the specified class.
344     */
345    private static SocketFactory getSocketFactory(String sfClass)
346				throws ClassNotFoundException,
347				    NoSuchMethodException,
348				    IllegalAccessException,
349				    InvocationTargetException {
350	if (sfClass == null || sfClass.length() == 0)
351	    return null;
352
353	// dynamically load the class 
354
355	ClassLoader cl = getContextClassLoader();
356	Class clsSockFact = null;
357	if (cl != null) {
358	    try {
359		clsSockFact = Class.forName(sfClass, false, cl);
360	    } catch (ClassNotFoundException cex) { }
361	}
362	if (clsSockFact == null)
363	    clsSockFact = Class.forName(sfClass);
364	// get & invoke the getDefault() method
365	Method mthGetDefault = clsSockFact.getMethod("getDefault", 
366						     new Class[]{});
367	SocketFactory sf = (SocketFactory)
368	    mthGetDefault.invoke(new Object(), new Object[]{});
369	return sf;
370    }
371
372    /**
373     * Start TLS on an existing socket.
374     * Supports the "STARTTLS" command in many protocols.
375     * This version for compatibility with possible third party code
376     * that might've used this API even though it shouldn't.
377     */
378    public static Socket startTLS(Socket socket) throws IOException {
379	return startTLS(socket, new Properties(), "socket");
380    }
381
382    /**
383     * Start TLS on an existing socket.
384     * Supports the "STARTTLS" command in many protocols.
385     * This version for compatibility with possible third party code
386     * that might've used this API even though it shouldn't.
387     */
388    public static Socket startTLS(Socket socket, Properties props,
389				String prefix) throws IOException {
390	InetAddress a = socket.getInetAddress();
391	String host = a.getHostName();
392	return startTLS(socket, host, props, prefix);
393    }
394
395    /**
396     * Start TLS on an existing socket.
397     * Supports the "STARTTLS" command in many protocols.
398     */
399    public static Socket startTLS(Socket socket, String host, Properties props,
400				String prefix) throws IOException {
401	int port = socket.getPort();
402	if (logger.isLoggable(Level.FINER))
403	    logger.finer("startTLS host " + host + ", port " + port);
404
405	String sfErr = "unknown socket factory";
406	try {
407	    SSLSocketFactory ssf = null;
408	    SocketFactory sf = null;
409
410	    // first, look for an SSL socket factory
411	    Object sfo = props.get(prefix + ".ssl.socketFactory");
412	    if (sfo instanceof SocketFactory) {
413		sf = (SocketFactory)sfo;
414		sfErr = "SSL socket factory instance " + sf;
415	    }
416	    if (sf == null) {
417		String sfClass =
418		    props.getProperty(prefix + ".ssl.socketFactory.class");
419		sf = getSocketFactory(sfClass);
420		sfErr = "SSL socket factory class " + sfClass;
421	    }
422	    if (sf != null && sf instanceof SSLSocketFactory)
423		ssf = (SSLSocketFactory)sf;
424
425	    // next, look for a regular socket factory that happens to be
426	    // an SSL socket factory
427	    if (ssf == null) {
428		sfo = props.get(prefix + ".socketFactory");
429		if (sfo instanceof SocketFactory) {
430		    sf = (SocketFactory)sfo;
431		    sfErr = "socket factory instance " + sf;
432		}
433		if (sf == null) {
434		    String sfClass =
435			props.getProperty(prefix + ".socketFactory.class");
436		    sf = getSocketFactory(sfClass);
437		    sfErr = "socket factory class " + sfClass;
438		}
439		if (sf != null && sf instanceof SSLSocketFactory)
440		    ssf = (SSLSocketFactory)sf;
441	    }
442
443	    // finally, use the default SSL socket factory
444	    if (ssf == null) {
445		String trusted;
446		if ((trusted = props.getProperty(prefix + ".ssl.trust")) !=
447			null) {
448		    try {
449			MailSSLSocketFactory msf = new MailSSLSocketFactory();
450			if (trusted.equals("*"))
451			    msf.setTrustAllHosts(true);
452			else
453			    msf.setTrustedHosts(trusted.split("\\s+"));
454			ssf = msf;
455			sfErr = "mail SSL socket factory";
456		    } catch (GeneralSecurityException gex) {
457			IOException ioex = new IOException(
458					"Can't create MailSSLSocketFactory");
459			ioex.initCause(gex);
460			throw ioex;
461		    }
462		} else {
463		    ssf = (SSLSocketFactory)SSLSocketFactory.getDefault();
464		    sfErr = "default SSL socket factory";
465		}
466	    }
467
468	    socket = ssf.createSocket(socket, host, port, true);
469	    configureSSLSocket(socket, host, props, prefix, ssf);
470	} catch (Exception ex) {
471	    if (ex instanceof InvocationTargetException) {
472		Throwable t =
473		  ((InvocationTargetException)ex).getTargetException();
474		if (t instanceof Exception)
475		    ex = (Exception)t;
476	    }
477	    if (ex instanceof IOException)
478		throw (IOException)ex;
479	    // wrap anything else before sending it on
480	    IOException ioex = new IOException(
481				"Exception in startTLS using " + sfErr +
482				": host, port: " +
483				host + ", " + port +
484				"; Exception: " + ex);
485	    ioex.initCause(ex);
486	    throw ioex;
487	}
488	return socket;
489    }
490
491    /**
492     * Configure the SSL options for the socket (if it's an SSL socket),
493     * based on the mail.<protocol>.ssl.protocols and
494     * mail.<protocol>.ssl.ciphersuites properties.
495     * Check the identity of the server as specified by the
496     * mail.<protocol>.ssl.checkserveridentity property.
497     */
498    private static void configureSSLSocket(Socket socket, String host,
499			Properties props, String prefix, SocketFactory sf)
500			throws IOException {
501	if (!(socket instanceof SSLSocket))
502	    return;
503	SSLSocket sslsocket = (SSLSocket)socket;
504
505	String protocols = props.getProperty(prefix + ".ssl.protocols", null);
506	if (protocols != null)
507	    sslsocket.setEnabledProtocols(stringArray(protocols));
508	else {
509	    /*
510	     * At least the UW IMAP server insists on only the TLSv1
511	     * protocol for STARTTLS, and won't accept the old SSLv2
512	     * or SSLv3 protocols.  Here we enable only the TLSv1
513	     * protocol.  XXX - this should probably be parameterized.
514	     */
515	    sslsocket.setEnabledProtocols(new String[] {"TLSv1"});
516	}
517	String ciphers = props.getProperty(prefix + ".ssl.ciphersuites", null);
518	if (ciphers != null)
519	    sslsocket.setEnabledCipherSuites(stringArray(ciphers));
520	if (logger.isLoggable(Level.FINER)) {
521	    logger.finer("SSL protocols after " +
522		Arrays.asList(sslsocket.getEnabledProtocols()));
523	    logger.finer("SSL ciphers after " +
524		Arrays.asList(sslsocket.getEnabledCipherSuites()));
525	}
526
527	/*
528	 * Force the handshake to be done now so that we can report any
529	 * errors (e.g., certificate errors) to the caller of the startTLS
530	 * method.
531	 */
532	sslsocket.startHandshake();
533
534	/*
535	 * Check server identity and trust.
536	 */
537	boolean idCheck = PropUtil.getBooleanProperty(props,
538			    prefix + ".ssl.checkserveridentity", false);
539	if (idCheck)
540	    checkServerIdentity(host, sslsocket);
541	if (sf instanceof MailSSLSocketFactory) {
542	    MailSSLSocketFactory msf = (MailSSLSocketFactory)sf;
543	    if (!msf.isServerTrusted(host, sslsocket)) {
544		try {
545		    sslsocket.close();
546		} finally {
547		    throw new IOException("Server is not trusted: " + host);
548		}
549	    }
550	}
551    }
552
553    /**
554     * Check the server from the Socket connection against the server name(s)
555     * as expressed in the server certificate (RFC 2595 check).
556     * 
557     * @param	server		name of the server expected
558     * @param   sslSocket	SSLSocket connected to the server
559     * @return  true if the RFC 2595 check passes
560     */
561    private static void checkServerIdentity(String server, SSLSocket sslSocket)
562				throws IOException {
563
564	// Check against the server name(s) as expressed in server certificate
565	try {
566	    java.security.cert.Certificate[] certChain =
567		      sslSocket.getSession().getPeerCertificates();
568	    if (certChain != null && certChain.length > 0 &&
569		    certChain[0] instanceof X509Certificate &&
570		    matchCert(server, (X509Certificate)certChain[0]))
571		return;
572	} catch (SSLPeerUnverifiedException e) {
573	    sslSocket.close();
574	    IOException ioex = new IOException(
575		"Can't verify identity of server: " + server);
576	    ioex.initCause(e);
577	    throw ioex;
578	}
579
580	// If we get here, there is nothing to consider the server as trusted.
581	sslSocket.close();
582	throw new IOException("Can't verify identity of server: " + server);
583    }
584
585    /**
586     * Do any of the names in the cert match the server name?
587     *  
588     * @param	server	name of the server expected
589     * @param   cert	X509Certificate to get the subject's name from
590     * @return  true if it matches
591     */
592    private static boolean matchCert(String server, X509Certificate cert) {
593	if (logger.isLoggable(Level.FINER))
594	    logger.finer("matchCert server " +
595		server + ", cert " + cert);
596
597	/*
598	 * First, try to use sun.security.util.HostnameChecker,
599	 * which exists in Sun's JDK starting with 1.4.1.
600	 * We use reflection to access it in case it's not available
601	 * in the JDK we're running on.
602	 */
603	try {
604	    Class hnc = Class.forName("sun.security.util.HostnameChecker");
605	    // invoke HostnameChecker.getInstance(HostnameChecker.TYPE_LDAP)
606	    // HostnameChecker.TYPE_LDAP == 2
607	    // LDAP requires the same regex handling as we need
608	    Method getInstance = hnc.getMethod("getInstance", 
609					new Class[] { byte.class });
610	    Object hostnameChecker = getInstance.invoke(new Object(),
611					new Object[] { Byte.valueOf((byte)2) });
612
613	    // invoke hostnameChecker.match( server, cert)
614	    if (logger.isLoggable(Level.FINER))
615		logger.finer("using sun.security.util.HostnameChecker");
616	    Method match = hnc.getMethod("match",
617			new Class[] { String.class, X509Certificate.class });
618	    try {
619		match.invoke(hostnameChecker, new Object[] { server, cert });
620		return true;
621	    } catch (InvocationTargetException cex) {
622		logger.log(Level.FINER, "FAIL", cex);
623		return false;
624	    }
625	} catch (Exception ex) {
626	    logger.log(Level.FINER, "NO sun.security.util.HostnameChecker", ex);
627	    // ignore failure and continue below
628	}
629
630	/*
631	 * Lacking HostnameChecker, we implement a crude version of
632	 * the same checks ourselves.
633	 */
634	try {
635	    /*
636	     * Check each of the subjectAltNames.
637	     * XXX - only checks DNS names, should also handle
638	     * case where server name is a literal IP address
639	     */
640	    Collection names = cert.getSubjectAlternativeNames();
641	    if (names != null) {
642		boolean foundName = false;
643		for (Iterator it = names.iterator(); it.hasNext(); ) {
644		    List nameEnt = (List)it.next();
645		    Integer type = (Integer)nameEnt.get(0);
646		    if (type.intValue() == 2) {	// 2 == dNSName
647			foundName = true;
648			String name = (String)nameEnt.get(1);
649			if (logger.isLoggable(Level.FINER))
650			    logger.finer("found name: " + name);
651			if (matchServer(server, name))
652			    return true;
653		    }
654		}
655		if (foundName)	// found a name, but no match
656		    return false;
657	    }
658	} catch (CertificateParsingException ex) {
659	    // ignore it
660	}
661
662	// XXX - following is a *very* crude parse of the name and ignores
663	//	 all sorts of important issues such as quoting
664	Pattern p = Pattern.compile("CN=([^,]*)");
665	Matcher m = p.matcher(cert.getSubjectX500Principal().getName());
666	if (m.find() && matchServer(server, m.group(1).trim()))
667	    return true;
668
669	return false;
670    }
671
672    /**
673     * Does the server we're expecting to connect to match the
674     * given name from the server's certificate?
675     *
676     * @param	server		name of the server expected
677     * @param	name		name from the server's certificate
678     */
679    private static boolean matchServer(String server, String name) {
680	if (logger.isLoggable(Level.FINER))
681	    logger.finer("match server " + server + " with " + name);
682	if (name.startsWith("*.")) {
683	    // match "foo.example.com" with "*.example.com"
684	    String tail = name.substring(2);
685	    if (tail.length() == 0)
686		return false;
687	    int off = server.length() - tail.length();
688	    if (off < 1)
689		return false;
690	    // if tail matches and is preceeded by "."
691	    return server.charAt(off - 1) == '.' &&
692		    server.regionMatches(true, off, tail, 0, tail.length());
693	} else
694	    return server.equalsIgnoreCase(name);
695    }
696
697    /**
698     * Parse a string into whitespace separated tokens
699     * and return the tokens in an array.
700     */
701    private static String[] stringArray(String s) {
702	StringTokenizer st = new StringTokenizer(s);
703	List tokens = new ArrayList();
704	while (st.hasMoreTokens())
705	    tokens.add(st.nextToken());
706	return (String[])tokens.toArray(new String[tokens.size()]);
707    }
708
709    /**
710     * Convenience method to get our context class loader.
711     * Assert any privileges we might have and then call the
712     * Thread.getContextClassLoader method.
713     */
714    private static ClassLoader getContextClassLoader() {
715	return (ClassLoader)
716		AccessController.doPrivileged(new PrivilegedAction() {
717	    public Object run() {
718		ClassLoader cl = null;
719		try {
720		    cl = Thread.currentThread().getContextClassLoader();
721		} catch (SecurityException ex) { }
722		return cl;
723	    }
724	});
725    }
726}