view rt/net/sourceforge/jnlp/runtime/JNLPClassLoader.java @ 2028:1db6ba4a4593

RH672262, CVE-2011-0025: IcedTea jarfile signature verification bypass 2011-01-24 Deepak Bhole <dbhole@redhat.com> RH672262, CVE-2011-0025: IcedTea jarfile signature verification bypass * rt/net/sourceforge/jnlp/runtime/JNLPClassLoader.java (initializeResources): Prompt user only if there is a single certificate that signs all jars in the jnlp file, otherwise treat as unsigned. * rt/net/sourceforge/jnlp/security/CertVerifier.java: Rename getCerts to getCertPath and make it return a CertPath. * rt/net/sourceforge/jnlp/security/CertsInfoPane.java: Rename certs variable to certPath and change its type to CertPath. (buildTree): Use new certPath variable. (populateTable): Same. * rt/net/sourceforge/jnlp/security/HttpsCertVerifier.java: Rename getCerts to getCertPath and make it return a CertPath. * rt/net/sourceforge/jnlp/tools/JarSigner.java: Change type for certs variable to be a hashmap that stores certs and the number of entries they have signed. (totalSignableEntries): New variable to track how many signable entries have been encountered. (getCerts): Updated method to return certs from new hashmap. (isFullySignedByASingleCert): New method. Returns if there is a single cert that signs all the entries in the jars specified in the jnlp file. (verifyJars): Move verifiedJars and unverifiedJars out of the for loop so that the data is not lost when the next jar is processed. After verifying each jar, see if there is a single signer, and prompt the user if there is such an untrusted signer. (verifyJar): Increment totalSignableEntries for each signable entry encountered and the count for each cert when it signs an entry. Move checkTrustedCerts() out of the function into verifyJars().
author Andrew John Hughes <ahughes@redhat.com>
date Tue, 25 Jan 2011 15:39:52 +0000
parents e508d944ca9d
children 4cb5a70fef4d
line wrap: on
line source


// 
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library 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
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


package net.sourceforge.jnlp.runtime;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.AllPermission;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.TreeSet;
import java.util.Vector;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import net.sourceforge.jnlp.ExtensionDesc;
import net.sourceforge.jnlp.JARDesc;
import net.sourceforge.jnlp.JNLPFile;
import net.sourceforge.jnlp.LaunchException;
import net.sourceforge.jnlp.ParseException;
import net.sourceforge.jnlp.PluginBridge;
import net.sourceforge.jnlp.ResourcesDesc;
import net.sourceforge.jnlp.SecurityDesc;
import net.sourceforge.jnlp.Version;
import net.sourceforge.jnlp.cache.CacheUtil;
import net.sourceforge.jnlp.cache.ResourceTracker;
import net.sourceforge.jnlp.cache.UpdatePolicy;
import net.sourceforge.jnlp.security.SecurityWarningDialog;
import net.sourceforge.jnlp.tools.JarSigner;
import sun.misc.JarIndex;

/**
 * Classloader that takes it's resources from a JNLP file.  If the
 * JNLP file defines extensions, separate classloaders for these
 * will be created automatically.  Classes are loaded with the
 * security context when the classloader was created.
 *
 * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
 * @version $Revision: 1.20 $ 
 */
public class JNLPClassLoader extends URLClassLoader {

    // todo: initializePermissions should get the permissions from
    // extension classes too so that main file classes can load
    // resources in an extension.

    /** shortcut for resources */
    private static String R(String key) { return JNLPRuntime.getMessage(key); }

    /** map from JNLPFile url to shared classloader */
    private static Map urlToLoader = new HashMap(); // never garbage collected!

    /** the directory for native code */
    private File nativeDir = null; // if set, some native code exists

    /** a list of directories that contain native libraries */
    private List<File> nativeDirectories = Collections.synchronizedList(new LinkedList<File>());

    /** security context */
    private AccessControlContext acc = AccessController.getContext();

    /** the permissions for the cached jar files */
    private List resourcePermissions;

    /** the app */
    private ApplicationInstance app = null; // here for faster lookup in security manager

    /** list of this, local and global loaders this loader uses */
    private JNLPClassLoader loaders[] = null; // ..[0]==this

    /** whether to strictly adhere to the spec or not */
    private boolean strict = true;

    /** loads the resources */
    private ResourceTracker tracker = new ResourceTracker(true); // prefetch

    /** the update policy for resources */
    private UpdatePolicy updatePolicy;

    /** the JNLP file */
    private JNLPFile file;

    /** the resources section */
    private ResourcesDesc resources;

    /** the security section */
    private SecurityDesc security;
    
    /** Permissions granted by the user during runtime. */
    private ArrayList<Permission> runtimePermissions = new ArrayList<Permission>();

    /** all jars not yet part of classloader or active */
    private List available = new ArrayList();

	/** all of the jar files that were verified */
	private ArrayList<String> verifiedJars = null;

	/** all of the jar files that were not verified */
	private ArrayList<String> unverifiedJars = null;

	/** the jarsigner tool to verify our jars */
	private JarSigner js = null;

	private boolean signing = false;
	
	/** ArrayList containing jar indexes for various jars available to this classloader */
	private ArrayList<JarIndex> jarIndexes = new ArrayList<JarIndex>();
	
	/** File entries in the jar files available to this classloader */
	private TreeSet jarEntries = new TreeSet();

	/** Map of specific codesources to securitydesc */
	private HashMap<URL, SecurityDesc> jarLocationSecurityMap = new HashMap<URL, SecurityDesc>();
	
    /**
     * Create a new JNLPClassLoader from the specified file.
     *
     * @param file the JNLP file
     */
    protected JNLPClassLoader(JNLPFile file, UpdatePolicy policy) throws LaunchException {
        super(new URL[0], JNLPClassLoader.class.getClassLoader());

        if (JNLPRuntime.isDebug())
            System.out.println("New classloader: "+file.getFileLocation());

        this.file = file;
        this.updatePolicy = policy;
        this.resources = file.getResources();

        // initialize extensions
        initializeExtensions();

        // initialize permissions
        initializePermissions();

        initializeResources();

        setSecurity();

    }

    private void setSecurity() throws LaunchException {
		
        URL codebase = null;

        if (file.getCodeBase() != null) {
            codebase = file.getCodeBase();
        } else {
            //Fixme: codebase should be the codebase of the Main Jar not 
            //the location. Although, it still works in the current state.
            codebase = file.getResources().getMainJAR().getLocation();
        }

        /**
         * When we're trying to load an applet, file.getSecurity() will return
         * null since there is no jnlp file to specify permissions. We
         * determine security settings here, after trying to verify jars.
         */
        if (file instanceof PluginBridge) {
            if (signing == true) {
                this.security = new SecurityDesc(file, 
                    SecurityDesc.ALL_PERMISSIONS,
                    codebase.getHost());
            } else {
                this.security = new SecurityDesc(file, 
                    SecurityDesc.SANDBOX_PERMISSIONS, 
                    codebase.getHost());
            }
        } else { //regular jnlp file
			
            /*
             * Various combinations of the jars being signed and <security> tags being
             * present are possible. They are treated as follows
             * 
             * Jars          JNLP File         Result
             * 
             * Signed        <security>        Appropriate Permissions
             * Signed        no <security>     Sandbox
             * Unsigned      <security>        Error
             * Unsigned      no <security>     Sandbox
             * 
             */

            if (!file.getSecurity().getSecurityType().equals(SecurityDesc.SANDBOX_PERMISSIONS) && !signing) {
                throw new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LUnsignedJarWithSecurity"), R("LUnsignedJarWithSecurityInfo"));
            }
            else if (signing == true) {
                this.security = file.getSecurity();
            } else {
                this.security = new SecurityDesc(file, 
                        SecurityDesc.SANDBOX_PERMISSIONS, 
                        codebase.getHost());
            }
        }
    }
    
    /**
     * Returns a JNLP classloader for the specified JNLP file.
     *
     * @param file the file to load classes for
     * @param policy the update policy to use when downloading resources
     */
    public static JNLPClassLoader getInstance(JNLPFile file, UpdatePolicy policy) throws LaunchException {
        JNLPClassLoader baseLoader = null;
        JNLPClassLoader loader = null;
        String uniqueKey = file.getUniqueKey();

        if (uniqueKey != null)
            baseLoader = (JNLPClassLoader) urlToLoader.get(uniqueKey);

		try {
		    
		    // If base loader is null, or the baseloader's file and this 
		    // file is different, initialize a new loader
		    if (baseLoader == null || 
		        !baseLoader.getJNLPFile().getFileLocation().equals(file.getFileLocation())) {

		        loader = new JNLPClassLoader(file, policy);

		        // New loader init may have caused extentions to create a 
		        // loader for this unique key. Check.
		        JNLPClassLoader extLoader = (JNLPClassLoader) urlToLoader.get(uniqueKey);

		        if (extLoader != null && extLoader != loader) {

		        	if (loader.signing && !extLoader.signing)
		        		if (!SecurityWarningDialog.showNotAllSignedWarningDialog(file))
		        			throw new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LSignedAppJarUsingUnsignedJar"), R("LSignedAppJarUsingUnsignedJarInfo"));

		        	loader.merge(extLoader);
		        }

                // loader is now current + ext. But we also need to think of 
                // the baseLoader
		        if (baseLoader != null && baseLoader != loader) {
		        	loader.merge(baseLoader);
                }

		    } else {
		        // if key is same and locations match, this is the loader we want
		        loader = baseLoader;
		    }

		} catch (LaunchException e) {
			throw e;
		}

        // loaders are mapped to a unique key. Only extensions and parent 
        // share a key, so it is safe to always share based on it
        urlToLoader.put(uniqueKey, loader);

        return loader;
    }

    /**
     * Returns a JNLP classloader for the JNLP file at the specified
     * location. 
     *
     * @param location the file's location
     * @param version the file's version
     * @param policy the update policy to use when downloading resources
     */
    public static JNLPClassLoader getInstance(URL location, String uniqueKey, Version version, UpdatePolicy policy)
            throws IOException, ParseException, LaunchException {
        JNLPClassLoader loader = (JNLPClassLoader) urlToLoader.get(uniqueKey);

        if (loader == null || !location.equals(loader.getJNLPFile().getFileLocation()))
            loader = getInstance(new JNLPFile(location, uniqueKey, version, false, policy), policy);

        return loader;
    }

    /**
     * Load the extensions specified in the JNLP file.
     */
    void initializeExtensions() {
        ExtensionDesc[] ext = resources.getExtensions();

        List loaderList = new ArrayList();

        loaderList.add(this);

		//if (ext != null) {
        	for (int i=0; i < ext.length; i++) {
            	try {
                    String uniqueKey = this.getJNLPFile().getUniqueKey();
                    JNLPClassLoader loader = getInstance(ext[i].getLocation(), uniqueKey, ext[i].getVersion(), updatePolicy);
                    loaderList.add(loader);
            	}
            	catch (Exception ex) {
                	ex.printStackTrace();
            	}
        	}
		//}

        loaders = (JNLPClassLoader[]) loaderList.toArray(new JNLPClassLoader[ loaderList.size()]);
    }

    /**
     * Make permission objects for the classpath.
     */
    void initializePermissions() {
        resourcePermissions = new ArrayList();

        JARDesc jars[] = resources.getJARs();
        for (int i=0; i < jars.length; i++) {
            Permission p = CacheUtil.getReadPermission(jars[i].getLocation(),
                                                       jars[i].getVersion());

            if (JNLPRuntime.isDebug()) {
            	if (p == null)
            		System.out.println("Unable to add permission for " + jars[i].getLocation());
            	else
            		System.out.println("Permission added: " + p.toString());
            }
            if (p != null)
                resourcePermissions.add(p);
        }
    }

    /**
     * Load all of the JARs used in this JNLP file into the
     * ResourceTracker for downloading.
     */
    void initializeResources() throws LaunchException {
        JARDesc jars[] = resources.getJARs();
		if (jars == null || jars.length == 0)
			return;
		/*
		if (jars == null || jars.length == 0) {
			throw new LaunchException(null, null, R("LSFatal"),
			                    R("LCInit"), R("LFatalVerification"), "No jars!");
		}
		*/
        List initialJars = new ArrayList();

        for (int i=0; i < jars.length; i++) {

            available.add(jars[i]);

            if (jars[i].isEager())
                initialJars.add(jars[i]); // regardless of part

            tracker.addResource(jars[i].getLocation(),
                                jars[i].getVersion(), 
                                jars[i].isCacheable() ? JNLPRuntime.getDefaultUpdatePolicy() : UpdatePolicy.FORCE
                               );
        }

        if (strict)
            fillInPartJars(initialJars); // add in each initial part's lazy jars

		if (JNLPRuntime.isVerifying()) {

			JarSigner js;
			waitForJars(initialJars); //download the jars first.

			try {
				js = verifyJars(initialJars);
			} catch (Exception e) {
				//we caught an Exception from the JarSigner class.
				//Note: one of these exceptions could be from not being able
				//to read the cacerts or trusted.certs files.
				e.printStackTrace();
				throw new LaunchException(null, null, R("LSFatal"),
					R("LCInit"), R("LFatalVerification"), R("LFatalVerificationInfo"));
			}

			//Case when at least one jar has some signing
			if (js.anyJarsSigned() && js.isFullySignedByASingleCert()){
				signing = true;

				if (!js.allJarsSigned() && 
					!SecurityWarningDialog.showNotAllSignedWarningDialog(file))
        			throw new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LSignedAppJarUsingUnsignedJar"), R("LSignedAppJarUsingUnsignedJarInfo"));
				
				//user does not trust this publisher
				if (!js.getAlreadyTrustPublisher()) {
				    checkTrustWithUser(js);
				} else {
					/**
					 * If the user trusts this publisher (i.e. the publisher's certificate
					 * is in the user's trusted.certs file), we do not show any dialogs.
					 */
				}
			} else {

				signing = false;
				//otherwise this jar is simply unsigned -- make sure to ask
				//for permission on certain actions
			}
		}
		
		for (JARDesc jarDesc: file.getResources().getJARs()) {
			try {
				URL location = tracker.getCacheFile(jarDesc.getLocation()).toURI().toURL();
				SecurityDesc jarSecurity = file.getSecurity();
				
				if (file instanceof PluginBridge) {
					
			        URL codebase = null;

			        if (file.getCodeBase() != null) {
			            codebase = file.getCodeBase();
			        } else {
			            //Fixme: codebase should be the codebase of the Main Jar not 
			            //the location. Although, it still works in the current state.
			            codebase = file.getResources().getMainJAR().getLocation();
			        }
					
					jarSecurity = new SecurityDesc(file, 
							SecurityDesc.ALL_PERMISSIONS,
							codebase.getHost());
				}
				
				jarLocationSecurityMap.put(location, jarSecurity);
			} catch (MalformedURLException mfe) {
				System.err.println(mfe.getMessage());
			}
		}

        activateJars(initialJars);
    }

    private void checkTrustWithUser(JarSigner js) throws LaunchException {
        if (!js.getRootInCacerts()) { //root cert is not in cacerts
            boolean b = SecurityWarningDialog.showCertWarningDialog(
                SecurityWarningDialog.AccessType.UNVERIFIED, file, js);
            if (!b)
                throw new LaunchException(null, null, R("LSFatal"), 
                    R("LCLaunching"), R("LNotVerified"), "");
        } else if (js.getRootInCacerts()) { //root cert is in cacerts
            boolean b = false;
            if (js.noSigningIssues())
                b = SecurityWarningDialog.showCertWarningDialog(
                        SecurityWarningDialog.AccessType.VERIFIED, file, js);
            else if (!js.noSigningIssues())
                b = SecurityWarningDialog.showCertWarningDialog(
                        SecurityWarningDialog.AccessType.SIGNING_ERROR, file, js);
            if (!b)
                throw new LaunchException(null, null, R("LSFatal"),
                    R("LCLaunching"), R("LCancelOnUserRequest"), "");
        }
    }

    /**
     * Add applet's codebase URL.  This allows compatibility with
     * applets that load resources from their codebase instead of
     * through JARs, but can slow down resource loading.  Resources
     * loaded from the codebase are not cached.
     */
    public void enableCodeBase() {
        addURL( file.getCodeBase() ); // nothing happens if called more that once?
    }

    /**
     * Sets the JNLP app this group is for; can only be called once.
     */
    public void setApplication(ApplicationInstance app) {
        if (this.app != null) {
            if (JNLPRuntime.isDebug()) {
                Exception ex = new IllegalStateException("Application can only be set once");
                ex.printStackTrace();
            }
            return;
        }

        this.app = app;
    }

    /**
     * Returns the JNLP app for this classloader
     */
    public ApplicationInstance getApplication() {
        return app;
    }

    /**
     * Returns the JNLP file the classloader was created from.
     */
    public JNLPFile getJNLPFile() {
        return file;
    }

    /**
     * Returns the permissions for the CodeSource.
     */
    protected PermissionCollection getPermissions(CodeSource cs) {
        Permissions result = new Permissions();

        // should check for extensions or boot, automatically give all
        // access w/o security dialog once we actually check certificates.

        // copy security permissions from SecurityDesc element
	 if (security != null) {
            // Security desc. is used only to track security settings for the
            // application. However, an application may comprise of multiple
            // jars, and as such, security must be evaluated on a per jar basis.
            
            // set default perms
            PermissionCollection permissions = security.getSandBoxPermissions();
            
            // If more than default is needed:
            // 1. Code must be signed
            // 2. ALL or J2EE permissions must be requested (note: plugin requests ALL automatically)
            if (cs.getCodeSigners() != null &&
                    (getCodeSourceSecurity(cs.getLocation()).getSecurityType().equals(SecurityDesc.ALL_PERMISSIONS) ||
                     getCodeSourceSecurity(cs.getLocation()).getSecurityType().equals(SecurityDesc.J2EE_PERMISSIONS))
            	    ) {

                permissions = getCodeSourceSecurity(cs.getLocation()).getPermissions();
            }

            Enumeration<Permission> e = permissions.elements();
            while (e.hasMoreElements())
                result.add(e.nextElement());
        }

        // add in permission to read the cached JAR files
        for (int i=0; i < resourcePermissions.size(); i++)
            result.add((Permission) resourcePermissions.get(i));

        // add in the permissions that the user granted.
        for (int i=0; i < runtimePermissions.size(); i++)
        	result.add(runtimePermissions.get(i));

        return result;
    }

    protected void addPermission(Permission p) {
    	runtimePermissions.add(p);
    }
    
    /**
     * Adds to the specified list of JARS any other JARs that need
     * to be loaded at the same time as the JARs specified (ie, are
     * in the same part).
     */
    protected void fillInPartJars(List jars) {
        for (int i=0; i < jars.size(); i++) {
            String part = ((JARDesc) jars.get(i)).getPart();

            for (int a=0; a < available.size(); a++) {
                JARDesc jar = (JARDesc) available.get(a);

                if (part != null && part.equals(jar.getPart()))
                    if (!jars.contains(jar))
                        jars.add(jar);
            }
        }
    }

    /**
     * Ensures that the list of jars have all been transferred, and
     * makes them available to the classloader.  If a jar contains
     * native code, the libraries will be extracted and placed in
     * the path.
     *
     * @param jars the list of jars to load
     */
    protected void activateJars(final List jars) {
        PrivilegedAction activate = new PrivilegedAction() {

            public Object run() {
                // transfer the Jars
                waitForJars(jars);

                for (int i=0; i < jars.size(); i++) {
                    JARDesc jar = (JARDesc) jars.get(i);

                    available.remove(jar);

                    // add jar
                    File localFile = tracker.getCacheFile(jar.getLocation());
                    try {
                        URL location = jar.getLocation(); // non-cacheable, use source location
                        if (localFile != null) {
                            location = localFile.toURL(); // cached file
                            
                            // This is really not the best way.. but we need some way for 
                            // PluginAppletViewer::getCachedImageRef() to check if the image 
                            // is available locally, and it cannot use getResources() because 
                            // that prefetches the resource, which confuses MediaTracker.waitForAll() 
                            // which does a wait(), waiting for notification (presumably 
                            // thrown after a resource is fetched). This bug manifests itself
                            // particularly when using The FileManager applet from Webmin.
                            
                            JarFile jarFile = new JarFile(localFile);
                            Enumeration e = jarFile.entries();
                            while (e.hasMoreElements()) {
                                
                                JarEntry je = (JarEntry) e.nextElement();
                                
                                // another jar in my jar? it is more likely than you think  
                                if (je.getName().endsWith(".jar")) {
                                    // We need to extract that jar so that it can be loaded 
                                    // (inline loading with "jar:..!/..." path will not work 
                                    // with standard classloader methods)

                                    String extractedJarLocation = localFile.getParent() + "/" + je.getName();
                                    File parentDir = new File(extractedJarLocation).getParentFile();
                                    if (!parentDir.isDirectory() && !parentDir.mkdirs()) {
                                        throw new RuntimeException(R("RNestedJarExtration"));
                                    }
                                    FileOutputStream extractedJar = new FileOutputStream(extractedJarLocation);
                                    InputStream is = jarFile.getInputStream(je);

                                    byte[] bytes = new byte[1024];
                                    int read = is.read(bytes);
                                    int fileSize = read;
                                    while (read > 0) {
                                        extractedJar.write(bytes, 0, read);
                                        read = is.read(bytes);
                                        fileSize += read;
                                    }

                                    is.close();
                                    extractedJar.close();
                                    
                                    // 0 byte file? skip
                                    if (fileSize <= 0) {
                                    	continue;
                                    }

                                    JarSigner signer = new JarSigner();
                                    signer.verifyJar(extractedJarLocation);

                                    if (signer.anyJarsSigned() && !signer.getAlreadyTrustPublisher()) {
                                        checkTrustWithUser(signer);
                                    }

                                    try {
                                        URL fileURL = new URL("file://" + extractedJarLocation); 
                                        addURL(fileURL);
                                        
                                        SecurityDesc jarSecurity = file.getSecurity();
                        				
                                        if (file instanceof PluginBridge) {
                        					
                                            URL codebase = null;

                                            if (file.getCodeBase() != null) {
                                                codebase = file.getCodeBase();
                                            } else {
                                                //Fixme: codebase should be the codebase of the Main Jar not 
                                                //the location. Although, it still works in the current state.
                                                codebase = file.getResources().getMainJAR().getLocation();
                                            }
                        					
                                            jarSecurity = new SecurityDesc(file, 
                                                SecurityDesc.ALL_PERMISSIONS,
                                                codebase.getHost());
                                        }
                        				
                                        jarLocationSecurityMap.put(fileURL, jarSecurity);
                                        
                                     } catch (MalformedURLException mfue) {
                                        if (JNLPRuntime.isDebug())
                                            System.err.println("Unable to add extracted nested jar to classpath");

                                        mfue.printStackTrace();
                                    }
                                }

                                jarEntries.add(je.getName());
                            }

                        }

                        addURL(location);

                        // there is currently no mechanism to cache files per 
                        // instance.. so only index cached files
                        if (localFile != null) {
                            JarIndex index = JarIndex.getJarIndex(new JarFile(localFile.getAbsolutePath()), null);

                            if (index != null)
                                jarIndexes.add(index);
                        }

                        if (JNLPRuntime.isDebug())
                            System.err.println("Activate jar: "+location);
                    }
                    catch (Exception ex) {
                        if (JNLPRuntime.isDebug())
                            ex.printStackTrace();
                    }

                    // some programs place a native library in any jar
                    activateNative(jar);
                }

                return null;
            }
        };

        AccessController.doPrivileged(activate, acc);
    }

    /**
     * Search for and enable any native code contained in a JAR by copying the
     * native files into the filesystem. Called in the security context of the
     * classloader.
     */
    protected void activateNative(JARDesc jar) {
        if (JNLPRuntime.isDebug())
            System.out.println("Activate native: "+jar.getLocation());

        File localFile = tracker.getCacheFile(jar.getLocation());
        if (localFile == null)
            return;

        if (nativeDir == null)
            nativeDir = getNativeDir();

        String[] librarySuffixes = { ".so", ".dylib", ".jnilib", ".framework", ".dll" };

        try {
            JarFile jarFile = new JarFile(localFile, false);
            Enumeration<JarEntry> entries = jarFile.entries();

            while (entries.hasMoreElements()) {
                JarEntry e = entries.nextElement();

                if (e.isDirectory()) {
                    continue;
                }

                String name = new File(e.getName()).getName();
                boolean isLibrary = false;

                for (String suffix: librarySuffixes) {
                    if (name.endsWith(suffix)) {
                       isLibrary = true;
                       break;
                    }
                }
                if (!isLibrary) {
                    continue;
                }

                File outFile = new File(nativeDir, name);

                CacheUtil.streamCopy(jarFile.getInputStream(e),
                                     new FileOutputStream(outFile));
            }
        }
        catch (IOException ex) {
            if (JNLPRuntime.isDebug())
                ex.printStackTrace();
        }
    }

    /**
     * Return the base directory to store native code files in.
     * This method does not need to return the same directory across
     * calls.
     */
    protected File getNativeDir() {
        nativeDir = new File(System.getProperty("java.io.tmpdir") 
                             + File.separator + "netx-native-" 
                             + (new Random().nextInt() & 0xFFFF));

        if (!nativeDir.mkdirs()) 
            return null;
        else {
            // add this new native directory to the search path
            addNativeDirectory(nativeDir);
            return nativeDir;
        }
    }

    /**
     * Adds the {@link File} to the search path of this {@link JNLPClassLoader}
     * when trying to find a native library
     */
    protected void addNativeDirectory(File nativeDirectory) {
        nativeDirectories.add(nativeDirectory);
    }

    /**
     * Returns a list of all directories in the search path of the current classloader
     * when it tires to find a native library.
     * @return a list of directories in the search path for native libraries
     */
    protected List<File> getNativeDirectories() {
        return nativeDirectories;
    }

    /**
     * Return the absolute path to the native library.
     */
    protected String findLibrary(String lib) {
        String syslib = System.mapLibraryName(lib);

        for (File dir: getNativeDirectories()) {
            File target = new File(dir, syslib);
            if (target.exists())
                return target.toString();
        }

        String result = super.findLibrary(lib);
        if (result != null)
            return result;

        return findLibraryExt(lib);
    }

    /**
     * Try to find the library path from another peer classloader.
     */
    protected String findLibraryExt(String lib) {
        for (int i=0; i < loaders.length; i++) {
            String result = null;

            if (loaders[i] != this)
                result = loaders[i].findLibrary(lib);

            if (result != null)
                return result;
        }

        return null;
    }

    /**
     * Wait for a group of JARs, and send download events if there
     * is a download listener or display a progress window otherwise.
     *
     * @param jars the jars
     */
    private void waitForJars(List jars) {
        URL urls[] = new URL[jars.size()];

        for (int i=0; i < jars.size(); i++) {
            JARDesc jar = (JARDesc) jars.get(i);

            urls[i] = jar.getLocation();
        }

        CacheUtil.waitForResources(app, tracker, urls, file.getTitle());
    }

    /**
	 * Verifies code signing of jars to be used.
	 *
	 * @param jars the jars to be verified.
	 */
	private JarSigner verifyJars(List<JARDesc> jars) throws Exception {
	
		js = new JarSigner();
		js.verifyJars(jars, tracker);
		return js;
	}

    /**
     * Find the loaded class in this loader or any of its extension loaders.
     */
    protected Class findLoadedClassAll(String name) {
        for (int i=0; i < loaders.length; i++) {
            Class result = null;

            if (loaders[i] == this)
                result = super.findLoadedClass(name);
            else
                result = loaders[i].findLoadedClassAll(name);

            if (result != null)
                return result;
        }

        return null;
    }

    /**
     * Find a JAR in the shared 'extension' classloaders, this
     * classloader, or one of the classloaders for the JNLP file's
     * extensions.
     */
    public Class loadClass(String name) throws ClassNotFoundException {

        Class result = findLoadedClassAll(name);

        // try parent classloader
        if (result == null) {
            try {
                ClassLoader parent = getParent();
                if (parent == null)
                    parent = ClassLoader.getSystemClassLoader();

                return parent.loadClass(name);
            }
            catch (ClassNotFoundException ex) { }
        }

        // filter out 'bad' package names like java, javax
        // validPackage(name);

        // search this and the extension loaders
        if (result == null)
            try {
                result = loadClassExt(name);
            } catch (ClassNotFoundException cnfe) {

                // Not found in external loader either. As a last resort, look in any available indexes

                // Currently this loads jars directly from the site. We cannot cache it because this 
                // call is initiated from within the applet, which does not have disk read/write permissions
                for (JarIndex index: jarIndexes) {
                    LinkedList<String> jarList = index.get(name.replace('.', '/'));

                    if (jarList != null) {
                        for (String jarName: jarList) {
                            JARDesc desc;
                            try {
                                desc = new JARDesc(new URL(file.getCodeBase(), jarName),
                                        null, null, false, true, false, true);
                            } catch (MalformedURLException mfe) {
                                throw new ClassNotFoundException(name);
                            }

                            available.add(desc);

                            tracker.addResource(desc.getLocation(),
                                    desc.getVersion(), 
                                    JNLPRuntime.getDefaultUpdatePolicy()
                            );

                            URL remoteURL;
                            try {
                                remoteURL = new URL(file.getCodeBase() + jarName);
                            } catch (MalformedURLException mfe) {
                                throw new ClassNotFoundException(name);
                            }

                            URL u;

                            try {
                                u = tracker.getCacheURL(remoteURL);
                            } catch (Exception e) {
                                throw new ClassNotFoundException(name);
                            }

                            if (u != null)
                                addURL(u);

                        }

                        // If it still fails, let it error out                        
                        result = loadClassExt(name);
                    }
                }
            }

        return result;
    }

    /**
     * Find the class in this loader or any of its extension loaders.
     */
    protected Class findClass(String name) throws ClassNotFoundException {
        for (int i=0; i < loaders.length; i++) {
            try {
                if (loaders[i] == this)
                    return super.findClass(name);
                else
                    return loaders[i].findClass(name);
            }
            catch(ClassNotFoundException ex) { }
            catch(ClassFormatError cfe) {}
        }

        throw new ClassNotFoundException(name);
    }

    /**
     * Search for the class by incrementally adding resources to the
     * classloader and its extension classloaders until the resource
     * is found.
     */
    private Class loadClassExt(String name) throws ClassNotFoundException {
        // make recursive
        addAvailable();

        // find it
        try {
            return findClass(name);
        }
        catch(ClassNotFoundException ex) {
        }

        // add resources until found
        while (true) {
            JNLPClassLoader addedTo = addNextResource();

            if (addedTo == null)
                throw new ClassNotFoundException(name);

            try {
                return addedTo.findClass(name);
            }
            catch(ClassNotFoundException ex) {
            }
        }
    }

    /**
     * Finds the resource in this, the parent, or the extension
     * class loaders.
     */
    public URL getResource(String name) {
        URL result = super.getResource(name);

        for (int i=1; i < loaders.length; i++)
            if (result == null)
                result = loaders[i].getResource(name);

        return result;
    }

    /**
     * Finds the resource in this, the parent, or the extension
     * class loaders.
     */
    public Enumeration findResources(String name) throws IOException {
        Vector resources = new Vector();

        for (int i=0; i < loaders.length; i++) {
            Enumeration e;

            if (loaders[i] == this)
                e = super.findResources(name);
            else 
                e = loaders[i].findResources(name);

            while (e.hasMoreElements())
                resources.add(e.nextElement());
        }

        return resources.elements();
    }
    
    /**
     * Returns if the specified resource is available locally from a cached jar
     * 
     * @param s The name of the resource
     * @return Whether or not the resource is available locally
     */
    public boolean resourceAvailableLocally(String s) {
        return jarEntries.contains(s);
    }

    /**
     * Adds whatever resources have already been downloaded in the
     * background.
     */
    protected void addAvailable() {
        // go through available, check tracker for it and all of its
        // part brothers being available immediately, add them.

        for (int i=1; i < loaders.length; i++) {
            loaders[i].addAvailable();
        }
    }

    /**
     * Adds the next unused resource to the classloader.  That
     * resource and all those in the same part will be downloaded
     * and added to the classloader before returning.  If there are
     * no more resources to add, the method returns immediately.
     *
     * @return the classloader that resources were added to, or null
     */
    protected JNLPClassLoader addNextResource() {
        if (available.size() == 0) {
            for (int i=1; i < loaders.length; i++) {
                JNLPClassLoader result = loaders[i].addNextResource();

                if (result != null)
                    return result;
            }
            return null;
        }

        // add jar
        List jars = new ArrayList();
        jars.add(available.get(0));

        fillInPartJars(jars);

		
		activateJars(jars);

        return this;
    }

    // this part compatibility with previous classloader
    /**
     * @deprecated
     */
    public String getExtensionName() {
        String result = file.getInformation().getTitle();

        if (result == null)
            result = file.getInformation().getDescription();
        if (result == null && file.getFileLocation() != null)
            result = file.getFileLocation().toString();
        if (result == null && file.getCodeBase() != null)
            result = file.getCodeBase().toString();

        return result;
    }

    /**
     * @deprecated
     */
    public String getExtensionHREF() {
        return file.getFileLocation().toString();
    }

	public boolean getSigning() {
		return signing;
	}

	protected SecurityDesc getSecurity() {
		return security;
	}
	
	/**
	 * Returns the security descriptor for given code source URL
	 * 
	 * @param source The code source
	 * @return The SecurityDescriptor for that source
	 */
	
	protected SecurityDesc getCodeSourceSecurity(URL source) {
		return jarLocationSecurityMap.get(source);
	}
	
	/**
	 * Merges the code source/security descriptor mapping from another loader
	 * 
	 * @param extLoader The loader form which to merge
	 * @throws SecurityException if the code is called from an untrusted source
	 */
	private void merge(JNLPClassLoader extLoader) {
		
		try {
			System.getSecurityManager().checkPermission(new AllPermission());
		} catch (SecurityException se) {
			throw new SecurityException("JNLPClassLoader() may only be called from trusted sources!");
		}

		// jars
		for (URL u : extLoader.getURLs())
        	addURL(u);
		
		// native search paths
        for (File nativeDirectory: extLoader.getNativeDirectories())
            addNativeDirectory(nativeDirectory);

        // security descriptors
		for (URL key: extLoader.jarLocationSecurityMap.keySet()) {
			jarLocationSecurityMap.put(key, extLoader.jarLocationSecurityMap.get(key));
		}
	}
}