You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by pa...@apache.org on 2008/03/07 01:37:47 UTC

svn commit: r634480 [1/3] - in /felix/trunk/framework/security: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/felix/ src/main/java/org/apache/felix/framework/ src/main/java/org/apache/felix/frame...

Author: pauls
Date: Thu Mar  6 16:37:30 2008
New Revision: 634480

URL: http://svn.apache.org/viewvc?rev=634480&view=rev
Log:
Initial version of the OSGi security layer. (FELIX-116, FELIX-115, FELIX-22)

Added:
    felix/trunk/framework/security/
    felix/trunk/framework/security/pom.xml
    felix/trunk/framework/security/src/
    felix/trunk/framework/security/src/main/
    felix/trunk/framework/security/src/main/java/
    felix/trunk/framework/security/src/main/java/org/
    felix/trunk/framework/security/src/main/java/org/apache/
    felix/trunk/framework/security/src/main/java/org/apache/felix/
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/SecurityActivator.java
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/SecurityProviderImpl.java
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/SecurityConstants.java
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionAdminImpl.java
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionInfoImpl.java
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/DomainGripper.java
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/permissionadmin/
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/permissionadmin/PermissionAdminImpl.java
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/BundleInputStream.java
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/Conditions.java
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/LocalPermissions.java
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/Permissions.java
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/PropertiesCache.java
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/TrustManager.java
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/verifier/
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/verifier/BundleDNParser.java
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/verifier/SignerMatcher.java
    felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/verifier/SubjectDNParser.java
    felix/trunk/framework/security/src/main/java/org/osgi/
    felix/trunk/framework/security/src/main/java/org/osgi/service/
    felix/trunk/framework/security/src/main/java/org/osgi/service/condpermadmin/
    felix/trunk/framework/security/src/main/java/org/osgi/service/condpermadmin/BundleLocationCondition.java
    felix/trunk/framework/security/src/main/java/org/osgi/service/condpermadmin/BundleSignerCondition.java

Added: felix/trunk/framework/security/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/pom.xml?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/pom.xml (added)
+++ felix/trunk/framework/security/pom.xml Thu Mar  6 16:37:30 2008
@@ -0,0 +1,66 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project>
+  <parent>
+    <groupId>org.apache.felix</groupId>
+    <artifactId>felix</artifactId>
+    <version>1.0.2</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <packaging>bundle</packaging>
+  <name>Apache Felix Security Provider</name>
+  <artifactId>org.apache.felix.framework.security</artifactId>
+  <version>0.9.0-SNAPSHOT</version>
+  <description>
+    This bundle provides an implementation of the OSGi security for Apache Felix.
+  </description>
+  <dependencies>
+     <dependency>
+      <groupId>${pom.groupId}</groupId>
+      <artifactId>org.osgi.core</artifactId>
+      <version>1.1.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>${pom.groupId}</groupId>
+      <artifactId>org.apache.felix.framework</artifactId>
+      <version>1.1.0-SNAPSHOT</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>1.4.0</version>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
+            <Export-Package>org.osgi.service.permissionadmin;-split-package:=merge-first, org.osgi.service.condpermadmin;-split-package:=merge-first</Export-Package>
+            <Private-Package>org.apache.felix.framework.*</Private-Package>
+            <Import-Package>!*</Import-Package>
+            <Fragment-Host>system.bundle; extension:=framework</Fragment-Host>
+            <Felix-Activator>org.apache.felix.framework.SecurityActivator</Felix-Activator>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>

Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/SecurityActivator.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/SecurityActivator.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/SecurityActivator.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/SecurityActivator.java Thu Mar  6 16:37:30 2008
@@ -0,0 +1,320 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.framework;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.Map.Entry;
+
+import org.apache.felix.framework.security.SecurityConstants;
+import org.apache.felix.framework.security.condpermadmin.ConditionalPermissionAdminImpl;
+import org.apache.felix.framework.security.permissionadmin.PermissionAdminImpl;
+import org.apache.felix.framework.security.util.Conditions;
+import org.apache.felix.framework.security.util.LocalPermissions;
+import org.apache.felix.framework.security.util.Permissions;
+import org.apache.felix.framework.security.util.PropertiesCache;
+import org.apache.felix.framework.security.verifier.BundleDNParser;
+import org.apache.felix.framework.util.SecureAction;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
+import org.osgi.service.permissionadmin.PermissionAdmin;
+
+/**
+ * <p>This Felix specific activator installs a security provider with the Felix
+ * framework. The security settings can be changed via the
+ * {@link PermissionAdmin} and/or the
+ * {@link ConditionalPermissionAdmin} services that may be published by
+ * this class.
+ * </p>
+ * <p>
+ * Permission informations as well as caching data will be stored in several 
+ * files in a directory called <tt>security</tt> obtained by a call to 
+ * {@link BundleContext#getDataFile(String))}.
+ * </p>
+ * <p>
+ * The following properties are recognized:
+ * <p>
+ * {@link SecurityConstants#ENABLE_PERMISSIONADMIN_PROP} - Whether or not 
+ * (<tt>true</tt>|<tt>false</tt>) to
+ * publish a{@link ConditionalPermissionAdmin} service. The default is
+ * {@link SecurityConstants#ENABLE_PERMISSIONADMIN_VALUE}.
+ * </p>
+ * <p>
+ * {@link SecurityConstants#ENABLE_CONDPERMADMIN_PROP} - Whether or not 
+ * (<tt>true</tt>|<tt>false</tt>) to
+ * publish a{@link ConditionalPermissionAdmin} service. The default is
+ * {@link SecurityConstants#ENABLE_CONDPERMADMIN_VALUE}.
+ * </p>
+ * <p>
+ * {@link SecurityConstants#KEYSTORE_FILE_PROP} - The keystore URL(s) to use as
+ * trusted CA stores. The urls must be separated by a guard (i.e., <tt>|</tt>).
+ * The default is
+ * {@link SecurityConstants#KEYSTORE_FILE_VALUE}.
+ * </p>
+ * <p>
+ * {@link SecurityConstants#KEYSTORE_PASS_PROP} - The keystore password(s) to
+ * use for the given keystores. The passwords must be separated by a guard 
+ * (i.e., <tt>|</tt>).The default is
+ * {@link SecurityConstants#KEYSTORE_PASS_VALUE}.
+ * </p>
+ * <p>
+ * {@link SecurityConstants#KEYSTORE_TYPE_PROP} - The keystore type(s) to use
+ * for the given keystores. The types must be separated by a guard 
+ * (i.e., <tt>|</tt>).The default is
+ * {@link SecurityConstants#KEYSTORE_TYPE_VALUE}.
+ * </p>
+ * <p>
+ * {@link SecurityConstants#CRL_FILE_PROP} - The CRL URL(s) to use for revoked
+ * certificates. The urls must be separated by a guard (i.e., <tt>|</tt>).
+ * The default is {@link SecurityConstants#CRL_FILE_VALUE}.
+ * </p>
+ * </p>
+ */
+/*
+ * TODO: using a string for passwords is bad. We need to investigate
+ * alternatives. 
+ * 
+ * TODO: we might want to allow for the recognized properties to
+ * change without a restart. This is trick because we can not publish a managed
+ * service due to not being able to import as we are an extension bundle.
+ */
+public final class SecurityActivator implements BundleActivator
+{
+    private SecurityProviderImpl m_provider = null;
+    private PropertiesCache m_dnsCache = null;
+    private PropertiesCache m_localCache = null;
+    private LocalPermissions m_localPermissions = null;
+
+    public synchronized void start(BundleContext context) throws Exception
+    {
+        PermissionAdminImpl pai = null;
+
+        SecureAction action = new SecureAction();
+
+        Permissions permissions = new Permissions(context, action);
+
+        File tmp = context.getDataFile("security" + File.separator + "tmp");
+        if ((tmp == null) || (!tmp.isDirectory() && !tmp.mkdirs()))
+        {
+            throw new IOException("Can't create tmp dir.");
+        }
+        // TODO: log something if we can not clean-up the tmp dir
+        File[] old = tmp.listFiles();
+        if (old != null)
+        {
+            for (int i = 0; i < old.length; i++)
+            {
+                old[i].delete();
+            }
+        }
+
+        if ("TRUE".equalsIgnoreCase(getProperty(context,
+            SecurityConstants.ENABLE_PERMISSIONADMIN_PROP,
+            SecurityConstants.ENABLE_PERMISSIONADMIN_VALUE)))
+        {
+            File cache =
+                context.getDataFile("security" + File.separator + "pa.txt");
+            if ((cache == null) || (!cache.isFile() && !cache.createNewFile()))
+            {
+                throw new IOException("Can't create cache file");
+            }
+            pai =
+                new PermissionAdminImpl(permissions, new PropertiesCache(cache,
+                    tmp, action));
+        }
+
+        ConditionalPermissionAdminImpl cpai = null;
+
+        if ("TRUE".equalsIgnoreCase(getProperty(context,
+            SecurityConstants.ENABLE_CONDPERMADMIN_PROP,
+            SecurityConstants.ENABLE_CONDPERMADMIN_VALUE)))
+        {
+            File cpaCache =
+                context.getDataFile("security" + File.separator + "cpa.txt");
+            if ((cpaCache == null) || (!cpaCache.isFile() && !cpaCache.createNewFile()))
+            {
+                throw new IOException("Can't create cache file");
+            }
+            File localCache = 
+                context.getDataFile("security" + File.separator + "local.txt");
+            if ((localCache == null) || (!localCache.isFile() && !localCache.createNewFile()))
+            {
+                throw new IOException("Can't create cache file");
+            }
+            
+            m_localCache = new PropertiesCache(localCache, tmp, action);
+            m_localPermissions  = new LocalPermissions(permissions, m_localCache);
+            
+            cpai =
+                new ConditionalPermissionAdminImpl(permissions, new Conditions(
+                    action), m_localPermissions,
+                    new PropertiesCache(cpaCache, tmp, action));
+        }
+
+        if ((pai != null) || (cpai != null))
+        {
+            String crlList =
+                getProperty(context, SecurityConstants.CRL_FILE_PROP,
+                    SecurityConstants.CRL_FILE_VALUE);
+            String storeList =
+                getProperty(context, SecurityConstants.KEYSTORE_FILE_PROP,
+                    SecurityConstants.KEYSTORE_FILE_VALUE);
+            String passwdList =
+                getProperty(context, SecurityConstants.KEYSTORE_PASS_PROP,
+                    SecurityConstants.KEYSTORE_PASS_VALUE);
+            String typeList =
+                getProperty(context, SecurityConstants.KEYSTORE_TYPE_PROP,
+                    SecurityConstants.KEYSTORE_TYPE_VALUE);
+
+            StringTokenizer storeTok = new StringTokenizer(storeList, "|");
+            StringTokenizer passwdTok = new StringTokenizer(passwdList, "|");
+            StringTokenizer typeTok = new StringTokenizer(typeList, "|");
+
+            if ((storeTok.countTokens() != passwdTok.countTokens())
+                || (passwdTok.countTokens() != typeTok.countTokens()))
+            {
+                throw new BundleException(
+                    "Each CACerts keystore must have one type and one passwd entry and vice versa.");
+            }
+
+            m_provider =
+                new SecurityProviderImpl(crlList, typeList, passwdList,
+                    storeList, pai, cpai, action);
+
+            File cache =
+                context.getDataFile("security" + File.separator + "dns.txt");
+            if ((cache == null) || (!cache.isFile() && !cache.createNewFile()))
+            {
+                throw new IOException("Can't create cache file");
+            }
+            m_dnsCache = new PropertiesCache(cache, tmp, action);
+
+            Map store = m_dnsCache.read(String[].class);
+
+            if (store != null)
+            {
+                BundleDNParser parser = m_provider.getParser();
+
+                for (Iterator iter = store.entrySet().iterator(); iter
+                    .hasNext();)
+                {
+                    Entry entry = (Entry) iter.next();
+                    String[] value = (String[]) entry.getValue();
+                    if ("none".equals(value[0]))
+                    {
+                        parser.put((String) entry.getKey(), null);
+                    }
+                    else if ("invalid".equals(value[0]))
+                    {
+                        parser.put((String) entry.getKey(), new String[0]);
+                    }
+                    else
+                    {
+                        parser.put((String) entry.getKey(), value);
+                    }
+                }
+            }
+            ((Felix) context.getBundle(0)).setSecurityProvider(m_provider);
+        }
+
+        if (pai != null)
+        {
+            context.registerService(PermissionAdmin.class.getName(), pai, null);
+        }
+
+        if (cpai != null)
+        {
+            context.registerService(ConditionalPermissionAdmin.class.getName(),
+                cpai, null);
+        }
+    }
+
+    public synchronized void stop(BundleContext context) throws Exception
+    {
+        if (m_provider != null)
+        {
+            m_dnsCache.write(write(m_provider.getParser().getCache(), context));
+        }
+        if (m_localPermissions != null)
+        {
+           m_localCache.write(write(m_localPermissions.getStore(), context));
+        }
+        m_provider = null;
+        m_dnsCache = null;
+        m_localPermissions = null;
+    }
+
+    private Map write(Map cache, BundleContext context)
+    {
+        // Filter the cached dn chains and only store the latest for each 
+        // bundle. This is ok because the framework will prune old revisions
+        // after a restart. The format is <id>-<timestamp>
+        Map store = new HashMap();
+        Map index = new HashMap();
+        for (Iterator iter = cache.entrySet().iterator(); iter.hasNext();)
+        {
+            Entry entry = (Entry) iter.next();
+            
+            String key = (String) entry.getKey();
+            String id = key.substring(0, key.indexOf("-"));
+            String time = key.substring(key.indexOf("-") + 1);
+            Bundle bundle = context.getBundle(Long.parseLong(id));
+            long timeLong = Long.parseLong(time);
+            if ((bundle == null) || 
+                ((FelixBundle) bundle).getInfo().getLastModified() > timeLong)
+            {
+                continue;
+            }
+            String last = (String) index.get(id);
+
+            if ((last == null)
+                || (Long.parseLong(last) < timeLong))
+            {
+                index.put(id, time);
+                Object[] dns = (Object[]) entry.getValue();
+                store.remove(id + "-" + last);
+                if ((dns != null) && (dns.length > 0))
+                {
+                    store.put(key, dns);
+                }
+                else
+                {
+                    store.put(key, (dns == null) ? new String[] {"none"} : new String[] {"invalid"});
+                }
+            }
+        }
+        return store;
+    }
+
+    private String getProperty(BundleContext context, String key,
+        String defaultValue)
+    {
+        String result = context.getProperty(key);
+
+        return ((result != null) && (result.trim().length() > 0)) ? result
+            : defaultValue;
+    }
+}

Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/SecurityProviderImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/SecurityProviderImpl.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/SecurityProviderImpl.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/SecurityProviderImpl.java Thu Mar  6 16:37:30 2008
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.framework;
+
+import java.security.Permission;
+import java.security.ProtectionDomain;
+
+import org.apache.felix.framework.ext.SecurityProvider;
+import org.apache.felix.framework.security.condpermadmin.ConditionalPermissionAdminImpl;
+import org.apache.felix.framework.security.permissionadmin.PermissionAdminImpl;
+import org.apache.felix.framework.security.util.TrustManager;
+import org.apache.felix.framework.security.verifier.BundleDNParser;
+import org.apache.felix.framework.security.verifier.SignerMatcher;
+import org.apache.felix.framework.util.SecureAction;
+import org.osgi.framework.Bundle;
+
+/**
+ * This class is the entry point to the security. It is used to determine whether
+ * a given bundle is signed correctely and has permissions based on 
+ * PermissionAdmin or ConditionalPermissionAdmin.
+ */
+public final class SecurityProviderImpl implements SecurityProvider
+{
+    private final BundleDNParser m_parser;
+    private final PermissionAdminImpl m_pai;
+    private final ConditionalPermissionAdminImpl m_cpai;
+    private final SecureAction m_action;
+
+    SecurityProviderImpl(String crlList, String typeList,
+        String passwdList, String storeList, PermissionAdminImpl pai,
+        ConditionalPermissionAdminImpl cpai, SecureAction action)
+    {
+        m_pai = pai;
+        m_cpai = cpai;
+        m_action = action;
+        m_parser =
+            new BundleDNParser(new TrustManager(crlList, typeList, passwdList,
+                storeList, m_action));
+    }
+
+    BundleDNParser getParser()
+    {
+        return m_parser;
+    }
+
+    /**
+     * If the given bundle is signed but can not be verified (e.g., missing files)
+     * then throw an exception.
+     */
+    public void checkBundle(Bundle bundle) throws Exception
+    {
+        BundleInfo info = ((FelixBundle) bundle).getInfo();
+
+        m_parser.checkDNChains(
+            (Long.toString(bundle.getBundleId()) + "-" + info.getArchive()
+                .getLastModified()), info.getCurrentModule().getContentLoader());
+    }
+
+    /**
+     * Get a signer matcher that can be used to match digital signed bundles.
+     */
+    public Object getSignerMatcher(final Bundle bundle)
+    {
+        return new SignerMatcher(Long.toString(bundle.getBundleId()),
+            ((FelixBundle) bundle).getInfo().getArchive(), m_parser);
+    }
+
+    /**
+     * If we have a permissionadmin then ask that one first and have it
+     * decide in case there is a location bound. If not then either use its 
+     * default permission in case there is no conditional permission admin
+     * or else ask that one. 
+     */
+    public boolean hasBundlePermission(ProtectionDomain bundleProtectionDomain,
+        Permission permission, boolean direct)
+    {
+        BundleProtectionDomain pd =
+            (BundleProtectionDomain) bundleProtectionDomain;
+        FelixBundle bundle = pd.getBundle();
+        BundleInfo info = bundle.getInfo();
+
+        if (info.getBundleId() == 0)
+        {
+            return true;
+        }
+
+        // TODO: using true, false, or null seems a bit awkward. Improve this.
+        Boolean result = null;
+        if (m_pai != null)
+        {
+            result =
+                m_pai.hasPermission(info.getLocation(), pd.getBundle(),
+                    permission, m_cpai, pd);
+        }
+
+        if (result != null)
+        {
+            return result.booleanValue();
+        }
+
+        if (m_cpai != null)
+        {
+            try
+            {
+                return m_cpai.hasPermission(bundle, 
+                    info.getCurrentModule().getContentLoader(), 
+                    bundle.getBundleId() + "-" + 
+                    info.getArchive().getLastModified(),null, pd,
+                    permission, direct);
+            }
+            catch (Exception e)
+            {
+                // TODO Auto-generated catch block
+                e.printStackTrace();
+            }
+        }
+
+        return false;
+    }
+}
\ No newline at end of file

Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/SecurityConstants.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/SecurityConstants.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/SecurityConstants.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/SecurityConstants.java Thu Mar  6 16:37:30 2008
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.framework.security;
+
+import java.io.File;
+
+public interface SecurityConstants
+{
+    public static final String KEYSTORE_FILE_PROP = "felix.keystore";
+
+    public static final String KEYSTORE_FILE_VALUE = "file:" +
+        System.getProperty("java.home") + File.separatorChar + "lib"
+            + File.separatorChar + "security" + File.separatorChar + "cacerts"
+            + "|file:" + System.getProperty("user.home") + File.separatorChar
+            + ".keystore";
+
+    public static final String KEYSTORE_TYPE_PROP = "felix.keystore.type";
+
+    public static final String KEYSTORE_TYPE_VALUE = "JKS" + "|" + "JKS";
+
+    public static final String KEYSTORE_PASS_PROP = "felix.keystore.pass";
+
+    public static final String KEYSTORE_PASS_VALUE =
+        "changeit" + "|" + "changeit";
+
+    public static final String CRL_FILE_PROP = "felix.crl";
+
+    public static final String CRL_FILE_VALUE = "";
+
+    public static final String ENABLE_CONDPERMADMIN_PROP =
+        "felix.security.conpermadmin";
+
+    public static final String ENABLE_CONDPERMADMIN_VALUE = "true";
+
+    public static final String ENABLE_PERMISSIONADMIN_PROP =
+        "felix.security.permissionadmin";
+
+    public static final String ENABLE_PERMISSIONADMIN_VALUE = "true";
+}

Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionAdminImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionAdminImpl.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionAdminImpl.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionAdminImpl.java Thu Mar  6 16:37:30 2008
@@ -0,0 +1,458 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.framework.security.condpermadmin;
+
+import java.io.IOException;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.DomainCombiner;
+import java.security.Permission;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.felix.framework.security.util.Conditions;
+import org.apache.felix.framework.security.util.LocalPermissions;
+import org.apache.felix.framework.security.util.Permissions;
+import org.apache.felix.framework.security.util.PropertiesCache;
+import org.apache.felix.framework.util.IteratorToEnumeration;
+import org.apache.felix.moduleloader.IContentLoader;
+import org.osgi.framework.Bundle;
+import org.osgi.service.condpermadmin.ConditionInfo;
+import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
+import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
+import org.osgi.service.permissionadmin.PermissionInfo;
+
+/**
+ * An implementation of the ConditionalPermissionAdmin service that doesn't need
+ * to have a framework specific security manager set. It use the DomainGripper
+ * to know what bundleprotectiondomains are expected.  
+ */
+public final class ConditionalPermissionAdminImpl implements
+    ConditionalPermissionAdmin
+{
+    private static final ConditionInfo[] EMPTY_CONDITION_INFO = new ConditionInfo[0];
+    private static final PermissionInfo[] EMPTY_PERMISSION_INFO = new PermissionInfo[0];
+    private final Map m_condPermInfos = new HashMap();
+    private final PropertiesCache m_propertiesCache;
+    private final Permissions m_permissions;
+    private final Conditions m_conditions;
+    private final LocalPermissions m_localPermissions;
+
+    public ConditionalPermissionAdminImpl(Permissions permissions,
+        Conditions condtions, LocalPermissions localPermissions,
+        PropertiesCache cache) throws IOException
+    {
+        m_propertiesCache = cache;
+        m_permissions = permissions;
+        m_conditions = condtions;
+        m_localPermissions = localPermissions;
+        // Now try to restore the cache.
+        Map old = m_propertiesCache.read(ConditionalPermissionInfoImpl.class);
+        if (old != null)
+        {
+            for (Iterator iter = old.entrySet().iterator(); iter.hasNext();)
+            {
+                Entry entry = (Entry) iter.next();
+                String name = (String) entry.getKey();
+                ConditionalPermissionInfoImpl cpi =
+                    ((ConditionalPermissionInfoImpl) entry.getValue());
+                m_condPermInfos.put(name, new ConditionalPermissionInfoImpl(
+                    name, cpi._getConditionInfos(), cpi._getPermissionInfos(),
+                    this));
+            }
+        }
+    }
+
+    public ConditionalPermissionInfo addConditionalPermissionInfo(
+        ConditionInfo[] conditions, PermissionInfo[] permissions)
+    {
+        Object sm = System.getSecurityManager();
+        if (sm != null)
+        {
+            ((SecurityManager) sm).checkPermission(Permissions.ALL_PERMISSION);
+        }
+        ConditionalPermissionInfoImpl result =
+            new ConditionalPermissionInfoImpl(notNull(conditions),
+                notNull(permissions), this);
+
+        return write(result.getName(), result);
+    }
+
+    ConditionalPermissionInfoImpl write(String name,
+        ConditionalPermissionInfoImpl cpi)
+    {
+        synchronized (m_propertiesCache)
+        {
+            Map tmp = null;
+
+            synchronized (m_condPermInfos)
+            {
+                tmp = new HashMap(m_condPermInfos);
+
+                if ((name != null) && (cpi != null))
+                {
+                    m_condPermInfos.put(name, cpi);
+                }
+                else if (name != null)
+                {
+                    m_condPermInfos.remove(name);
+                }
+                else
+                {
+                    tmp = null;
+                }
+            }
+
+            try
+            {
+                m_propertiesCache.write(m_condPermInfos);
+            }
+            catch (IOException ex)
+            {
+                synchronized (m_condPermInfos)
+                {
+                    m_condPermInfos.clear();
+                    m_condPermInfos.putAll(tmp);
+                }
+                ex.printStackTrace();
+                throw new IllegalStateException(ex.getMessage());
+            }
+        }
+        synchronized (m_condPermInfos)
+        {
+            return (ConditionalPermissionInfoImpl) m_condPermInfos.get(name);
+        }
+    }
+
+    // TODO: this is pretty much untested so it might not work like this
+    public AccessControlContext getAccessControlContext(String[] signers)
+    {
+        final String[] finalSigners =
+            (String[]) notNull(signers).toArray(new String[0]);
+        return new AccessControlContext(AccessController.getContext(),
+            new DomainCombiner()
+            {
+                public ProtectionDomain[] combine(ProtectionDomain[] arg0,
+                    ProtectionDomain[] arg1)
+                {
+                    return new ProtectionDomain[] { new ProtectionDomain(null,
+                        null)
+                    {
+                        public boolean implies(Permission permission)
+                        {
+                            return hasPermission(null, null, null, finalSigners,
+                                this, permission, true);
+                        }
+                    } };
+                }
+            });
+    }
+
+    public ConditionalPermissionInfo getConditionalPermissionInfo(String name)
+    {
+        if (name == null)
+        {
+            throw new IllegalArgumentException("Name may not be null");
+        }
+        ConditionalPermissionInfoImpl result = null;
+
+        synchronized (m_condPermInfos)
+        {
+            result = (ConditionalPermissionInfoImpl) m_condPermInfos.get(name);
+        }
+
+        if (result == null)
+        {
+            result = new ConditionalPermissionInfoImpl(this, name);
+
+            result = write(result.getName(), result);
+        }
+
+        return result;
+    }
+
+    public Enumeration getConditionalPermissionInfos()
+    {
+        synchronized (m_condPermInfos)
+        {
+            return new IteratorToEnumeration((new ArrayList(m_condPermInfos
+                .values())).iterator());
+        }
+    }
+
+    public ConditionalPermissionInfo setConditionalPermissionInfo(String name,
+        ConditionInfo[] conditions, PermissionInfo[] permissions)
+    {
+        Object sm = System.getSecurityManager();
+        if (sm != null)
+        {
+            ((SecurityManager) sm).checkPermission(Permissions.ALL_PERMISSION);
+        }
+
+        ConditionalPermissionInfoImpl result = null;
+        conditions = notNull(conditions);
+        permissions = notNull(permissions);
+
+        if (name != null)
+        {
+            synchronized (m_condPermInfos)
+            {
+                result =
+                    (ConditionalPermissionInfoImpl) m_condPermInfos.get(name);
+
+                if (result == null)
+                {
+                    result =
+                        new ConditionalPermissionInfoImpl(name, conditions,
+                            permissions, this);
+                }
+                else
+                {
+                    result.setConditionsAndPermissions(conditions, permissions);
+                }
+            }
+        }
+        else
+        {
+            result =
+                new ConditionalPermissionInfoImpl(conditions, permissions, this);
+        }
+
+        return write(result.getName(), result);
+    }
+
+    private PermissionInfo[] notNull(PermissionInfo[] permissions)
+    {
+        if (permissions == null)
+        {
+            return ConditionalPermissionInfoImpl.PERMISSION_INFO;
+        }
+        return (PermissionInfo[]) notNull((Object[]) permissions).toArray(
+            EMPTY_PERMISSION_INFO);
+    }
+
+    private ConditionInfo[] notNull(ConditionInfo[] conditions)
+    {
+        if (conditions == null)
+        {
+            return ConditionalPermissionInfoImpl.CONDITION_INFO;
+        }
+        return (ConditionInfo[]) notNull((Object[]) conditions).toArray(
+            EMPTY_CONDITION_INFO);
+    }
+
+    private List notNull(Object[] elements)
+    {
+        List result = new ArrayList();
+
+        for (int i = 0; i < elements.length; i++)
+        {
+            if (elements[i] != null)
+            {
+                result.add(elements[i]);
+            }
+        }
+
+        return result;
+    }
+
+    // The thread local stack used to keep track of bundle protection domains we
+    // still expect to see.
+    private final ThreadLocal m_stack = new ThreadLocal();
+
+    /**
+     * This method does the actual permission check. If it is not a direct check
+     * it will try to determine the other bundle domains that will follow 
+     * automatically in case this is the first check in one permission check.
+     * If not then it will keep track of which domains we have already see.
+     * While it keeps track it builds up a list of postponed tuples which
+     * it will evaluate at the last domain. See the core spec 9.5.1 and following
+     * for a general description.
+     * 
+     * @param felixBundle the bundle in question.
+     * @param loader the content loader of the bundle to get access to the jar 
+     *    to check for local permissions.
+     * @param root the bundle id.
+     * @param signers the signers (this is to support the ACC based on signers) 
+     * @param pd the bundle protection domain
+     * @param permission the permission currently checked
+     * @param direct whether this is a direct check or not. direct check will not
+     *     expect any further bundle domains on the stack
+     * @return true in case the permission is granted or there are postponed tuples
+     *     false if not. Again, see the spec for more explanations.
+     */
+    public boolean hasPermission(Bundle felixBundle, IContentLoader loader, String root, 
+        String[] signers, ProtectionDomain pd, Permission permission,
+        boolean direct)
+    {
+        // System.out.println(felixBundle + "-" + permission);
+        List domains = null;
+        List tuples = null;
+        Object[] entry = null;
+        // first see whether this is the normal case (the special case is for 
+        // the ACC based on signers).
+        if (signers == null)
+        {
+            // In case of  a direct call we don't need to look for other pds
+            if (direct)
+            {
+                domains = new ArrayList();
+                tuples = new ArrayList();
+                domains.add(pd);
+            }
+            else
+            {
+                // Get the other pds from the stck
+                entry = (Object[]) m_stack.get();
+
+                // if there are none then get them from the gripper
+                if (entry == null)
+                {
+                    entry =
+                        new Object[] { new ArrayList(DomainGripper.grep()),
+                            new ArrayList() };
+                }
+                else
+                {
+                    m_stack.set(null);
+                }
+
+                domains = (List) entry[0];
+                tuples = (List) entry[1];
+                if (!domains.contains(pd))
+                {
+                    // We have been called directly without the direct flag
+                    domains.clear();
+                    domains.add(pd);
+                }
+            }
+        }
+
+        // check the local permissions. they need to all the permission if there
+        // are any
+        if (!m_localPermissions.implies(root, loader, felixBundle, permission))
+        {
+            return false;
+        }
+
+        List posts = new ArrayList();
+
+        boolean result = eval(posts, felixBundle, signers, permission);
+
+        if (signers != null)
+        {
+            return result;
+        }
+
+        domains.remove(pd);
+
+        // We postponed tuples
+        if (!posts.isEmpty())
+        {
+            tuples.add(posts);
+        }
+
+        // Are we at the end or this was a direct call?
+        if (domains.isEmpty())
+        {
+            m_stack.set(null);
+            // Now eval the postponed tupels. if the previous eval did return false
+            // tuples will be empty so we don't return from here.
+            if (!tuples.isEmpty())
+            {
+                return m_conditions.evalRecursive(tuples);
+            }
+        }
+        else
+        {
+            // this is to support recursive permission checks. In case we trigger
+            // a permission check while eval the stack is null until this point
+            m_stack.set(entry);
+        }
+
+        return result;
+    }
+
+    // we need to find all conditions that apply and then check whether they
+    // de note the permission in question unless the conditions are postponed
+    // then we make sure their permissions imply the permission and add them
+    // to the list of posts. Return true in case we pass or have posts
+    // else falls and clear the posts first.
+    private boolean eval(List posts, Bundle bundle, String[] signers,
+        Permission permission)
+    {
+        List condPermInfos = null;
+
+        synchronized (m_condPermInfos)
+        {
+            if (m_condPermInfos.isEmpty())
+            {
+                return true;
+            }
+            condPermInfos = new ArrayList(m_condPermInfos.values());
+        }
+
+        // Check for implicit permissions like access to file area
+        if ((bundle != null)
+            && m_permissions.getPermissions(m_permissions.getImplicit(bundle))
+                .implies(permission, bundle))
+        {
+            return true;
+        }
+
+        // now do the real thing
+        for (Iterator iter = condPermInfos.iterator(); iter.hasNext();)
+        {
+            ConditionalPermissionInfoImpl cpi =
+                (ConditionalPermissionInfoImpl) iter.next();
+
+            ConditionInfo[] conditions = cpi._getConditionInfos();
+
+            List currentPosts = new ArrayList();
+
+            if (!m_conditions.getConditions(bundle, signers, conditions)
+                .isSatisfied(currentPosts))
+            {
+                continue;
+            }
+
+            if (!m_permissions.getPermissions(cpi._getPermissionInfos())
+                .implies(permission, null))
+            {
+                continue;
+            }
+
+            if (currentPosts.isEmpty())
+            {
+                posts.clear();
+                return true;
+            }
+
+            posts.add(currentPosts);
+        }
+
+        return !posts.isEmpty();
+    }
+}

Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionInfoImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionInfoImpl.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionInfoImpl.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/ConditionalPermissionInfoImpl.java Thu Mar  6 16:37:30 2008
@@ -0,0 +1,212 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.framework.security.condpermadmin;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.StringTokenizer;
+
+import org.apache.felix.framework.security.util.Permissions;
+import org.osgi.service.condpermadmin.ConditionInfo;
+import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
+import org.osgi.service.permissionadmin.PermissionInfo;
+
+/**
+ * Simple storage class for condperminfos. Additionally, this class can be used
+ * to encode and decode infos. 
+ */
+public final class ConditionalPermissionInfoImpl implements ConditionalPermissionInfo
+{
+    private static final Random RANDOM = new Random();
+    static final ConditionInfo[] CONDITION_INFO = new ConditionInfo[0];
+    static final PermissionInfo[] PERMISSION_INFO = new PermissionInfo[0];
+    private final Object m_lock = new Object();
+    private final String m_name;
+    private volatile ConditionalPermissionAdminImpl m_cpai;
+    private ConditionInfo[] m_conditions;
+    private PermissionInfo[] m_permissions;
+
+    public ConditionalPermissionInfoImpl(String encoded)
+    {
+        StringTokenizer tok = new StringTokenizer(encoded, "\n");
+        if (!tok.nextToken().trim().equals("{"))
+        {
+            throw new IllegalArgumentException();
+        }
+        m_cpai = null;
+        m_name = tok.nextToken().trim().substring(1);
+        List conditions = new ArrayList();
+        List permissions = new ArrayList();
+        for (String current = tok.nextToken().trim();; current =
+            tok.nextToken().trim())
+        {
+            if (current.equals("}"))
+            {
+                break;
+            }
+            else if (current.startsWith("["))
+            {
+                conditions.add(new ConditionInfo(current));
+            }
+            else if (current.startsWith("("))
+            {
+                permissions.add(new PermissionInfo(current));
+            }
+            else
+            {
+                if (!current.startsWith("#"))
+                {
+                    throw new IllegalArgumentException();
+                }
+            }
+        }
+
+        m_conditions =
+            conditions.isEmpty() ? CONDITION_INFO
+                : (ConditionInfo[]) conditions
+                    .toArray(new ConditionInfo[conditions.size()]);
+        m_permissions =
+            permissions.isEmpty() ? PERMISSION_INFO
+                : (PermissionInfo[]) permissions
+                    .toArray(new PermissionInfo[permissions.size()]);
+    }
+
+    public ConditionalPermissionInfoImpl(ConditionalPermissionAdminImpl cpai,
+        String name)
+    {
+        m_name = name;
+        m_cpai = cpai;
+        m_conditions = CONDITION_INFO;
+        m_permissions = PERMISSION_INFO;
+    }
+
+    public ConditionalPermissionInfoImpl(ConditionInfo[] conditions,
+        PermissionInfo[] permisions, ConditionalPermissionAdminImpl cpai)
+    {
+        m_name = Long.toString(RANDOM.nextLong() ^ System.currentTimeMillis());
+        m_cpai = cpai;
+        m_conditions = conditions;
+        m_permissions = permisions;
+    }
+
+    public ConditionalPermissionInfoImpl(String name,
+        ConditionInfo[] conditions, PermissionInfo[] permisions,
+        ConditionalPermissionAdminImpl cpai)
+    {
+        m_name = name;
+        m_conditions = conditions;
+        m_permissions = permisions;
+        m_cpai = cpai;
+    }
+
+    public void delete()
+    {
+        Object sm = System.getSecurityManager();
+        if (sm != null)
+        {
+            ((SecurityManager) sm).checkPermission(Permissions.ALL_PERMISSION);
+        }
+
+        synchronized (m_lock)
+        {
+            m_cpai.write(m_name, null);
+            m_conditions = CONDITION_INFO;
+            m_permissions = PERMISSION_INFO;
+        }
+    }
+
+    public ConditionInfo[] getConditionInfos()
+    {
+        synchronized (m_lock)
+        {
+            return (ConditionInfo[]) m_conditions.clone();
+        }
+    }
+
+    ConditionInfo[] _getConditionInfos()
+    {
+        synchronized (m_lock)
+        {
+            return m_conditions;
+        }
+    }
+
+    void setConditionsAndPermissions(ConditionInfo[] conditions,
+        PermissionInfo[] permissions)
+    {
+        synchronized (m_lock)
+        {
+            m_conditions = conditions;
+            m_permissions = permissions;
+        }
+    }
+
+    public String getName()
+    {
+        return m_name;
+    }
+
+    public PermissionInfo[] getPermissionInfos()
+    {
+        synchronized (m_lock)
+        {
+            return (PermissionInfo[]) m_permissions.clone();
+        }
+    }
+
+    PermissionInfo[] _getPermissionInfos()
+    {
+        synchronized (m_lock)
+        {
+            return m_permissions;
+        }
+    }
+
+    public String getEncoded()
+    {
+        StringBuffer buffer = new StringBuffer();
+        buffer.append('{');
+        buffer.append('\n');
+        buffer.append('#');
+        buffer.append(m_name);
+        buffer.append('\n');
+        synchronized (m_lock)
+        {
+            writeTo(m_conditions, buffer);
+            writeTo(m_permissions, buffer);
+        }
+        buffer.append('}');
+        return buffer.toString();
+    }
+
+    private void writeTo(Object[] elements, StringBuffer buffer)
+    {
+        for (int i = 0; i < elements.length; i++)
+        {
+            buffer.append(elements[i]);
+            buffer.append('\n');
+        }
+    }
+
+    public String toString()
+    {
+        return getEncoded();
+    }
+}

Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/DomainGripper.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/DomainGripper.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/DomainGripper.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/condpermadmin/DomainGripper.java Thu Mar  6 16:37:30 2008
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.framework.security.condpermadmin;
+
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.AllPermission;
+import java.security.DomainCombiner;
+import java.security.Permission;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.felix.framework.BundleProtectionDomain;
+
+/**
+ * This class is a hack to get all BundleProtectionDomains currently on the 
+ * security stack. This way we don't need to have our own security manager set.
+ */
+final class DomainGripper implements DomainCombiner, PrivilegedAction
+{
+    private static final ProtectionDomain[] ALL_PERMISSION_PD =
+        new ProtectionDomain[] { new ProtectionDomain(null, null)
+        {
+            public boolean implies(Permission perm)
+            {
+                return true;
+            }
+        } };
+
+    // A per thread cache of DomainGripper objects. We might want to wrap them
+    // in a softreference eventually
+    private static final ThreadLocal m_cache = new ThreadLocal();
+
+    private static final Permission ALL_PERMISSION = new AllPermission();
+
+    private final List m_domains = new ArrayList();
+
+    private AccessControlContext m_system = null;
+
+    /**
+     * Get all bundle protection domains and add them to the m_domains. Then 
+     * return the ALL_PERMISSION_PD. 
+     */
+    public ProtectionDomain[] combine(ProtectionDomain[] current,
+        ProtectionDomain[] assigned)
+    {
+        filter(current, m_domains);
+        filter(assigned, m_domains);
+
+        return ALL_PERMISSION_PD;
+    }
+
+    private void filter(ProtectionDomain[] assigned, List domains)
+    {
+        if (assigned != null)
+        {
+            for (int i = 0; i < assigned.length; i++)
+            {
+                if ((assigned[i].getClass() == BundleProtectionDomain.class)
+                    && !domains.contains(assigned[i]))
+                {
+                    domains.add(assigned[i]);
+                }
+            }
+        }
+    }
+
+    /**
+     * Get the current bundle protection domains on the stack up to the last 
+     * privileged call.
+     */
+    public static List grep()
+    {
+        // First try to get a cached version. We cache by thread.
+        DomainGripper gripper = (DomainGripper) m_cache.get();
+        if (gripper == null)
+        {
+            // there is none so create one and cache it
+            gripper = new DomainGripper();
+            m_cache.set(gripper);
+        }
+        else
+        {
+            // This thread has a cached version so prepare it
+            gripper.m_domains.clear();
+        }
+
+        // Get the current context.
+        gripper.m_system = AccessController.getContext();
+
+        // and merge it with the current combiner (i.e., gripper)
+        AccessControlContext context =
+            (AccessControlContext) AccessController.doPrivileged(gripper);
+
+        gripper.m_system = null;
+
+        // now get the protection domains
+        AccessController.doPrivileged(gripper, context);
+
+        // and return them
+        return gripper.m_domains;
+    }
+
+    public Object run()
+    {
+        // this is a call to merge with the current context.
+        if (m_system != null)
+        {
+            return new AccessControlContext(m_system, this);
+        }
+
+        // this is a call to get the protection domains.
+        AccessController.checkPermission(ALL_PERMISSION);
+        return null;
+    }
+}

Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/permissionadmin/PermissionAdminImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/permissionadmin/PermissionAdminImpl.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/permissionadmin/PermissionAdminImpl.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/permissionadmin/PermissionAdminImpl.java Thu Mar  6 16:37:30 2008
@@ -0,0 +1,291 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.framework.security.permissionadmin;
+
+import java.io.IOException;
+import java.security.AllPermission;
+import java.security.Permission;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.framework.security.condpermadmin.ConditionalPermissionAdminImpl;
+import org.apache.felix.framework.security.util.Permissions;
+import org.apache.felix.framework.security.util.PropertiesCache;
+import org.osgi.framework.Bundle;
+import org.osgi.service.permissionadmin.PermissionAdmin;
+import org.osgi.service.permissionadmin.PermissionInfo;
+
+/**
+ * This class is a relatively straight forward implementation of the PermissionAdmin service.
+ * The only somewhat involved thing is that it respects the presents of a
+ * conditionalpermissionadmin service as per spec.
+ */
+// TODO: Do we need this class at all or can we just emulate it using the condpermadmin?
+public final class PermissionAdminImpl implements PermissionAdmin
+{
+    private static final PermissionInfo[] ALL_PERMISSION =
+        new PermissionInfo[] { new PermissionInfo(
+            AllPermission.class.getName(), "", "") };
+    
+    private final Map m_store = new HashMap();
+
+    private final PropertiesCache m_cache;
+
+    private final Permissions m_permissions;
+
+    private PermissionInfo[] m_default = null;
+
+    public PermissionAdminImpl(Permissions permissions, PropertiesCache cache)
+        throws IOException
+    {
+        m_permissions = permissions;
+        m_cache = cache;
+        Map old = m_cache.read(PermissionInfo[].class);
+        if (old != null)
+        {
+            m_store.putAll(old);
+        }
+    }
+
+    public PermissionInfo[] getDefaultPermissions()
+    {
+        synchronized (m_store)
+        {
+            if (m_default == null)
+            {
+                return null;
+            }
+            return (PermissionInfo[]) m_default.clone();
+        }
+    }
+
+    public synchronized String[] getLocations()
+    {
+        synchronized (m_store)
+        {
+            if (m_store.isEmpty())
+            {
+                return null;
+            }
+
+            return (String[]) m_store.keySet().toArray(
+                new String[m_store.size()]);
+        }
+    }
+
+    public PermissionInfo[] getPermissions(String location)
+    {
+        synchronized (m_store)
+        {
+            if (m_store.containsKey(location))
+            {
+                return (PermissionInfo[]) ((PermissionInfo[]) m_store
+                    .get(location)).clone();
+            }
+            return null;
+        }
+    }
+
+    /**
+     * This will do the actual permission check as described in the core spec 10.2
+     * It will respect a present condpermadmin service as described in 9.10.
+     * 
+     * @param location the location of the bundle.
+     * @param bundle the bundle in question.
+     * @param permission the permission to check.
+     * @param cpai A condpermadmin if one is present else null.
+     * @param pd the protectiondomain
+     * @return Boolean.TRUE if the location is bound and the permission is 
+     *  granted or if there is no cpa and the default permissions imply the 
+     *  permission Boolean.FALSE otherwise unless the location is not bound and 
+     *  their is a cpa in which case null is returned.
+     */
+    public Boolean hasPermission(String location, Bundle bundle,
+        Permission permission, ConditionalPermissionAdminImpl cpai,
+        ProtectionDomain pd)
+    {
+        PermissionInfo[] permissions = null;
+        boolean file = false;
+        synchronized (m_store)
+        {
+            if (m_store.containsKey(location))
+            {
+                permissions = (PermissionInfo[]) m_store.get(location);
+                file = true;
+            }
+            else if (cpai == null)
+            {
+                if (m_default != null)
+                {
+                    permissions = m_default;
+                }
+                else
+                {
+                    permissions = ALL_PERMISSION;
+                }
+            }
+        }
+        if ((cpai == null) || file)
+        {
+            if (check(permissions, permission, file ? bundle : null))
+            {
+                return Boolean.TRUE;
+            }
+        }
+
+        permissions = m_permissions.getImplicit(bundle);
+
+        if (check(permissions, permission, bundle))
+        {
+            return Boolean.TRUE;
+        }
+
+        if ((cpai != null) && !file)
+        {
+            return null;
+        }
+        return Boolean.FALSE;
+    }
+
+    private boolean check(PermissionInfo[] permissions, Permission permission,
+        Bundle bundle)
+    {
+        Permissions permissionsObject =
+            m_permissions.getPermissions(permissions);
+
+        return permissionsObject.implies(permission, bundle);
+    }
+
+    public void setDefaultPermissions(PermissionInfo[] permissions)
+    {
+        Object sm = System.getSecurityManager();
+        if (sm != null)
+        {
+            ((SecurityManager) sm).checkPermission(Permissions.ALL_PERMISSION);
+        }
+
+        synchronized (m_cache)
+        {
+            PermissionInfo[] def = null;
+            Map store = null;
+            synchronized (m_store)
+            {
+                def = m_default;
+                store = new HashMap(m_store);
+
+                m_default = (permissions != null) ? notNull(permissions) : null;
+            }
+
+            try
+            {
+                m_cache.write(setDefaults(store, def));
+            }
+            catch (IOException ex)
+            {
+                synchronized (m_store)
+                {
+                    m_default = def;
+                }
+
+                ex.printStackTrace();
+                // TODO: log this
+                throw new IllegalStateException(ex.getMessage());
+            }
+        }
+    }
+
+    public void setPermissions(String location, PermissionInfo[] permissions)
+    {
+        Object sm = System.getSecurityManager();
+        if (sm != null)
+        {
+            ((SecurityManager) sm).checkPermission(Permissions.ALL_PERMISSION);
+        }
+
+        synchronized (m_cache)
+        {
+            if (location != null)
+            {
+                Map store = null;
+                Map storeCopy = null;
+                PermissionInfo[] def = null;
+                synchronized (m_store)
+                {
+                    storeCopy = new HashMap(m_store);
+                    if (permissions != null)
+                    {
+                        m_store.put(location, notNull(permissions));
+                    }
+                    else
+                    {
+                        m_store.remove(location);
+                    }
+                    store = new HashMap(m_store);
+                }
+                try
+                {
+                    m_cache.write(setDefaults(store, def));
+                }
+                catch (IOException ex)
+                {
+                    synchronized (m_store)
+                    {
+                        m_store.clear();
+                        m_store.putAll(storeCopy);
+                    }
+
+                    ex.printStackTrace();
+                    // TODO: log this
+                    throw new IllegalStateException(ex.getMessage());
+                }
+            }
+        }
+    }
+
+    private Map setDefaults(Map store, PermissionInfo[] def)
+    {
+        if (def != null)
+        {
+            store.put("DEFAULT", def);
+        }
+        else
+        {
+            store.remove("DEFAULT");
+        }
+        return store;
+    }
+
+    private PermissionInfo[] notNull(PermissionInfo[] permissions)
+    {
+        List result = new ArrayList();
+
+        for (int i = 0; i < permissions.length; i++)
+        {
+            if (permissions[i] != null)
+            {
+                result.add(permissions[i]);
+            }
+        }
+        return (PermissionInfo[]) result.toArray(new PermissionInfo[result
+            .size()]);
+    }
+}

Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/BundleInputStream.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/BundleInputStream.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/BundleInputStream.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/BundleInputStream.java Thu Mar  6 16:37:30 2008
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.framework.security.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+import org.apache.felix.framework.util.IteratorToEnumeration;
+import org.apache.felix.moduleloader.IContent;
+
+/**
+ * This class makes a given content available as a inputstream with a jar content.
+ * In other words the stream can be used as input to a JarInputStream. 
+ */
+public final class BundleInputStream extends InputStream
+{
+    private final IContent m_root;
+    private final Enumeration m_content;
+    private final OutputStreamBuffer m_outputBuffer = new OutputStreamBuffer();
+    
+    private ByteArrayInputStream m_buffer = null;
+    private JarOutputStream m_output = null;
+    
+    public BundleInputStream(IContent root) throws IOException 
+    {
+        m_root = root;
+        
+        List entries = new ArrayList();
+        
+        int count = 0;
+        String manifest = null;
+        for (Enumeration e = m_root.getEntries();e.hasMoreElements();)
+        {
+            String entry = (String) e.nextElement();
+            if (entry.equalsIgnoreCase("META-INF/MANIFEST.MF"))
+            {
+                if (manifest == null)
+                {
+                    manifest = entry;
+                }
+            }
+            else if (entry.toUpperCase().startsWith("META-INF/"))
+            {
+                entries.add(count++, entry);
+            }
+            else
+            {
+                entries.add(entry);
+            }
+        }
+        if (manifest == null)
+        {
+            manifest = "META-INF/MANIFEST.MF";
+        }
+        m_content = new IteratorToEnumeration(entries.iterator());
+        
+        try
+        {
+            m_output = new JarOutputStream(m_outputBuffer);
+            readNext(manifest);
+            m_buffer = new ByteArrayInputStream(
+                m_outputBuffer.m_outBuffer.toByteArray());
+
+            m_outputBuffer.m_outBuffer = null;
+        } 
+        catch (IOException ex)
+        {
+            // TODO: figure out what is wrong
+            ex.printStackTrace();
+            throw ex;
+        }
+    }
+
+    public int read() throws IOException
+    {
+        if ((m_output == null) && (m_buffer == null))
+        {
+            return -1;
+        }
+
+        if (m_buffer != null)
+        {
+            int result = m_buffer.read();
+
+            if (result == -1)
+            {
+                m_buffer = null;
+                return read();
+            }
+            
+            return result;
+        }
+
+        if (m_content.hasMoreElements())
+        {
+            String current = (String) m_content.nextElement();
+            
+            readNext(current);
+            
+            if (!m_content.hasMoreElements())
+            {
+                m_output.close();
+                m_output = null;
+            }
+
+            m_buffer = new ByteArrayInputStream(
+                m_outputBuffer.m_outBuffer.toByteArray());
+
+            m_outputBuffer.m_outBuffer = null;
+        }
+
+        return read();
+    }
+
+    private void readNext(String path) throws IOException
+    {
+        m_outputBuffer.m_outBuffer = new ByteArrayOutputStream();
+
+        InputStream in = null;
+        try
+        {
+            in = m_root.getEntryAsStream(path);
+
+            if (in == null)
+            {
+                throw new IOException("Missing entry");
+            }
+            
+            JarEntry entry = new JarEntry(path);
+
+            m_output.putNextEntry(entry);
+
+            byte[] buffer = new byte[4 * 1024];
+
+            for (int c = in.read(buffer); c != -1; c = in.read(buffer))
+            {
+                m_output.write(buffer, 0, c);
+            }
+        }
+        finally
+        {
+            if (in != null)
+            {
+                try
+                {
+                    in.close();
+                }
+                catch (Exception ex) 
+                {
+                    // Not much we can do
+                }
+            }
+        }
+
+        m_output.closeEntry();
+
+        m_output.flush();
+    }
+    
+    private static final class OutputStreamBuffer extends OutputStream
+    {
+        ByteArrayOutputStream m_outBuffer = null;
+
+        public void write(int b)
+        {
+            m_outBuffer.write(b);
+        }
+        
+        public void write(byte[] buffer) throws IOException
+        {
+            m_outBuffer.write(buffer);
+        }       
+        
+        public void write(byte[] buffer, int offset, int length)
+        {
+            m_outBuffer.write(buffer, offset, length);
+        }
+    }
+}

Added: felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/Conditions.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/Conditions.java?rev=634480&view=auto
==============================================================================
--- felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/Conditions.java (added)
+++ felix/trunk/framework/security/src/main/java/org/apache/felix/framework/security/util/Conditions.java Thu Mar  6 16:37:30 2008
@@ -0,0 +1,387 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.framework.security.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.Map.Entry;
+
+import org.apache.felix.framework.security.verifier.SignerMatcher;
+import org.apache.felix.framework.util.SecureAction;
+import org.osgi.framework.Bundle;
+import org.osgi.service.condpermadmin.BundleSignerCondition;
+import org.osgi.service.condpermadmin.Condition;
+import org.osgi.service.condpermadmin.ConditionInfo;
+
+/**
+ * This class caches conditions instances by their infos. Furthermore, it allows
+ * to eval postponed condition permission tuples as per spec (see 9.45).
+ */
+// TODO: maybe use bundle events instead of soft/weak references.
+public final class Conditions
+{
+    private static final ThreadLocal m_conditionStack = new ThreadLocal();
+
+    private final Map m_cache = new WeakHashMap();
+    
+    private final Bundle m_bundle;
+    private final String[] m_signers;
+
+    private final ConditionInfo[] m_conditionInfos;
+    private final Condition[] m_conditions;
+    private final SecureAction m_action;
+
+    public Conditions(SecureAction action)
+    {
+        this(null, null, null, action);
+    }
+    
+    private Conditions(Bundle bundle, String[] signers,
+        ConditionInfo[] conditions, SecureAction action)
+    {
+        m_bundle = bundle;
+        m_signers = signers;
+        m_conditionInfos = conditions;
+        m_conditions = ((conditions != null) && (bundle != null)) ? new Condition[m_conditionInfos.length] : null;
+        m_action = action;
+    }
+
+    public Conditions getConditions(Bundle bundle, String[] signers,
+        ConditionInfo[] conditions)
+    {
+        Conditions result = null;
+        Map index = null;
+        synchronized (m_cache)
+        {
+            index = (Map) m_cache.get(conditions);
+            if (index == null)
+            {
+                index = new WeakHashMap();
+                m_cache.put(conditions, index);
+            }
+        }
+        synchronized (index)
+        {
+            if (bundle != null)
+            {
+                result = (Conditions) index.get(bundle);
+            }
+        }
+        
+        if (result == null)
+        {
+            result = new Conditions(bundle, signers, conditions, m_action);
+            synchronized (index)
+            {
+                index.put(bundle, result);
+            }
+        }
+        
+        return result;
+    }
+
+    // See whether the given list is satisfied or not
+    public boolean isSatisfied(List posts)
+    {
+        for (int i = 0; i < m_conditions.length; i++)
+        {
+            if (m_bundle == null)
+            {
+                if (!m_conditionInfos[i].getType().equals(
+                    BundleSignerCondition.class.getName()))
+                {
+                    return false;
+                }
+                String[] args = m_conditionInfos[i].getArgs();
+
+                boolean match = false;
+                if (args.length == 0)
+                {
+                    for (int j = 0; j < m_signers.length; j++)
+                    {
+                        if (SignerMatcher.match(args[0], m_signers[j]))
+                        {
+                            match = true;
+                            break;
+                        }
+                    }
+                }
+                if (!match)
+                {
+                    return false;
+                }
+                continue;
+            }
+            try
+            {
+                Condition condition = null;
+                boolean add = false;
+                Class clazz = Class.forName(m_conditionInfos[i].getType());
+                
+                synchronized (m_conditionInfos)
+                {
+                    condition = m_conditions[i];
+                    
+                }
+                
+                if (condition == null)
+                {
+                    add = true;
+                    condition = createCondition(m_bundle, clazz, m_conditionInfos[i]);
+                }
+                
+                if (condition.isPostponed())
+                {
+                    posts.add(condition);
+                    if (add)
+                    {
+                        synchronized (m_conditionInfos)
+                        {
+                            if (m_conditions[i] == null)
+                            {
+                                m_conditions[i] = condition;
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    Object current = m_conditionStack.get();
+
+                    if (current == null)
+                    {
+                        m_conditionStack.set(clazz);
+                    }
+                    else
+                    {
+                        if (current instanceof HashSet)
+                        {
+                            if (((HashSet) current).contains(clazz))
+                            {
+                                return false;
+                            }
+                            ((HashSet) current).add(clazz);
+                        }
+                        else
+                        {
+                            if (current == clazz)
+                            {
+                                return false;
+                            }
+                            HashSet frame = new HashSet();
+                            frame.add(current);
+                            frame.add(clazz);
+                            m_conditionStack.set(frame);
+                            current = frame;
+                        }
+                    }
+                    try
+                    {
+                        boolean result = condition.isSatisfied();
+
+                        if (!condition.isMutable() && ((condition != Condition.TRUE) && (condition != Condition.FALSE)))
+                        {
+                            synchronized (m_conditionInfos)
+                            {
+                                m_conditions[i] = result ? Condition.TRUE : Condition.FALSE;
+                            }
+                        }
+                        else
+                        {
+                            synchronized (m_conditionInfos)
+                            {
+                                m_conditions[i] = condition;
+                            }
+                        }
+                        if (!result)
+                        {
+                            return false;
+                        }
+                    }
+                    finally
+                    {
+                        if (current == null)
+                        {
+                            m_conditionStack.set(null);
+                        }
+                        else
+                        {
+                            ((HashSet) current).remove(clazz);
+                            if (((HashSet) current).isEmpty())
+                            {
+                                m_conditionStack.set(null);
+                            }
+                        }
+                    }
+                }
+            }
+            catch (Exception e)
+            {
+                // TODO: log this as per spec
+                e.printStackTrace();
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean evalRecursive(List entries)
+    {
+        return _evalRecursive(entries, 0, new ArrayList(), new HashMap());
+    }
+
+    private boolean _evalRecursive(List entries, int pos, List acc, Map contexts)
+    {
+        if (pos == entries.size())
+        {
+            // we need to group by type by tuple
+            Map conditions = new HashMap();
+            for (Iterator iter = acc.iterator(); iter.hasNext();)
+            {
+                for (Iterator iter2 = ((List) iter.next()).iterator(); iter2
+                    .hasNext();)
+                {
+                    Object entry = iter2.next();
+                    Set group = (Set) conditions.get(entry.getClass());
+
+                    if (group == null)
+                    {
+                        group = new HashSet();
+                    }
+                    group.add(entry);
+
+                    conditions.put(entry.getClass(), group);
+                }
+            }
+
+            // and then eval per group
+            for (Iterator iter = conditions.entrySet().iterator(); iter.hasNext();)
+            {
+                Entry entry = (Entry) iter.next();
+                Class key = (Class) entry.getKey();
+                
+                Hashtable context = (Hashtable) contexts.get(key);
+                if (context == null)
+                {
+                    context = new Hashtable();
+                    contexts.put(key, context);
+                }
+                Set set = (Set) entry.getValue();
+                Condition[] current =
+                    (Condition[]) set.toArray(new Condition[set.size()]);
+
+                // We must be catching recursive evaluation as per spec, hence use a thread
+                // local stack to do so
+                Object currentCond = m_conditionStack.get();
+
+                if (currentCond == null)
+                {
+                    m_conditionStack.set(key);
+                }
+                else
+                {
+                    if (currentCond instanceof HashSet)
+                    {
+                        if (((HashSet) currentCond).contains(key))
+                        {
+                            return false;
+                        }
+                        ((HashSet) currentCond).add(key);
+                    }
+                    else
+                    {
+                        if (currentCond == key)
+                        {
+                            return false;
+                        }
+                        HashSet frame = new HashSet();
+                        frame.add(current);
+                        frame.add(key);
+                        m_conditionStack.set(frame);
+                        currentCond = frame;
+                    }
+                }
+                try
+                {
+                    if (!current[0].isSatisfied(current, context))
+                    {
+                        return false;
+                    }
+                }
+                finally
+                {
+                    if (currentCond == null)
+                    {
+                        m_conditionStack.set(null);
+                    }
+                    else
+                    {
+                        ((HashSet) currentCond).remove(key);
+                        if (((HashSet) currentCond).isEmpty())
+                        {
+                            m_conditionStack.set(null);
+                        }
+                    }
+                }
+            }
+            return true;
+        }
+
+        List entry = (List) entries.get(pos);
+
+        for (int i = 0; i < entry.size(); i++)
+        {
+            acc.add(entry.get(i));
+
+            if (_evalRecursive(entries, pos + 1, acc, contexts))
+            {
+                return true;
+            }
+
+            acc.remove(acc.size() - 1);
+        }
+
+        return false;
+    }
+
+    private Condition createCondition(final Bundle bundle, final Class clazz,
+        final ConditionInfo info) throws Exception
+    {
+        try
+        {
+            return (Condition) m_action.getMethod(clazz, "getCondition",
+                new Class[] { Bundle.class, ConditionInfo.class }).invoke(null,
+                new Object[] { bundle, info });
+        }
+        catch (Exception ex)
+        {
+            ex.printStackTrace();
+            return (Condition) m_action.getConstructor(clazz,
+                new Class[] { Bundle.class, ConditionInfo.class }).newInstance(
+                new Object[] { bundle, info });
+        }
+    }
+}