/* * 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.mosgi.jmx.rmiconnector.mx4j.remote; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Constructor; import java.security.AccessControlContext; import java.security.AccessController; import java.security.CodeSource; import java.security.DomainCombiner; import java.security.Permission; import java.security.PermissionCollection; import java.security.Principal; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.security.ProtectionDomain; import javax.security.auth.AuthPermission; import javax.security.auth.Policy; import javax.security.auth.Subject; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.management.remote.SubjectDelegationPermission; /** * * @author <a href="mailto:biorn_steedom@users.sourceforge.net">Simone Bordet</a> * @version $Revision: 1.2 $ */ public class MX4JRemoteUtils { private static int connectionNumber; /** * Returns a copy of the given Map that does not contain non-serializable entries */ public static Map removeNonSerializableEntries(Map map) { Map newMap = new HashMap(map.size()); for (Iterator i = map.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry)i.next(); if (isSerializable(entry)) newMap.put(entry.getKey(), entry.getValue()); } return newMap; } private static boolean isSerializable(Object object) { if (object instanceof Map.Entry) return isSerializable(((Map.Entry)object).getKey()) && isSerializable(((Map.Entry)object).getValue()); if (object == null) return true; if (object instanceof String) return true; if (object instanceof Number) return true; if (!(object instanceof Serializable)) return false; return isTrulySerializable(object); } public static boolean isTrulySerializable(Object object) { // Give up and serialize the object try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(object); oos.close(); return true; } catch (IOException ignored) { } return false; } public static String createConnectionID(String protocol, String callerAddress, int callerPort, Subject subject) { // See JSR 160 specification at javax/management/remote/package-summary.html StringBuffer buffer = new StringBuffer(protocol); buffer.append(':'); if (callerAddress != null) buffer.append("//").append(callerAddress); if (callerPort >= 0) buffer.append(':').append(callerPort); buffer.append(' '); if (subject != null) { Set principals = subject.getPrincipals(); for (Iterator i = principals.iterator(); i.hasNext();) { Principal principal = (Principal)i.next(); String name = principal.getName(); name = name.replace(' ', '_'); buffer.append(name); if (i.hasNext()) buffer.append(';'); } } buffer.append(' '); buffer.append("0x").append(Integer.toHexString(getNextConnectionNumber())); return buffer.toString(); } private static synchronized int getNextConnectionNumber() { return ++connectionNumber; } public static Object subjectInvoke(Subject subject, Subject delegate, AccessControlContext context, PrivilegedExceptionAction action) throws Exception { if (delegate != null) { if (subject == null) throw new SecurityException("There is no authenticated subject to delegate to"); checkSubjectDelegationPermission(delegate, getSubjectContext(subject, context)); } if (subject == null) { if (context == null) return action.run(); try { return AccessController.doPrivileged(action, context); } catch (PrivilegedActionException x) { throw x.getException(); } } try { AccessControlContext subjectContext = getSubjectContext(subject, context); return Subject.doAsPrivileged(subject, action, subjectContext); } catch (PrivilegedActionException x) { throw x.getException(); } } private static void checkSubjectDelegationPermission(final Subject delegate, AccessControlContext context) throws SecurityException { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { AccessController.doPrivileged(new PrivilegedAction() { public Object run() { StringBuffer buffer = new StringBuffer(); Set principals = delegate.getPrincipals(); for (Iterator i = principals.iterator(); i.hasNext();) { Principal principal = (Principal)i.next(); buffer.setLength(0); String permission = buffer.append(principal.getClass().getName()).append(".").append(principal.getName()).toString(); sm.checkPermission(new SubjectDelegationPermission(permission)); } return null; } }, context); } } /** * Returns a suitable AccessControlContext that restricts access in a {@link Subject#doAsPrivileged} call * based on the current JAAS authorization policy, and combined with the given context. * * This is needed because the server stack frames in a call to a JMXConnectorServer are, * for example for RMI, like this: * <pre> * java.lang.Thread.run() * [rmi runtime classes] * javax.management.remote.rmi.RMIConnectionImpl * [mx4j JSR 160 implementation code] * javax.security.auth.Subject.doAsPrivileged() * [mx4j JSR 160 implementation code] * [mx4j JSR 3 implementation code] * </pre> * All protection domains in this stack frames have AllPermission, normally, and the Subject.doAsPrivileged() * call stops the checks very early. <br> * * So we need a restricting context (created at the start() of the connector server), and furthermore we need * to combine the restricting context with a "special" context that does not have the same location as the * JSR 3 and 160 classes and implementation (in particular will have a null location). <br> * The "injection" of this synthetic ProtectionDomain allows to give AllPermission to the JSR 3 and 160 classes * and implementation, but still have the possibility to specify a JAAS policy with MBeanPermissions in this way: * <pre> * grant principal javax.management.remote.JMXPrincipal "mx4j" * { * permission javax.management.MBeanPermission "*", "getAttribute"; * }; * </pre> */ private static AccessControlContext getSubjectContext(final Subject subject, final AccessControlContext context) { final SecurityManager sm = System.getSecurityManager(); if (sm == null) { return context; } else { return (AccessControlContext)AccessController.doPrivileged(new PrivilegedAction() { public Object run() { InjectingDomainCombiner combiner = new InjectingDomainCombiner(subject); AccessControlContext acc = new AccessControlContext(context, combiner); AccessController.doPrivileged(new PrivilegedAction() { public Object run() { // Check this permission, that is required anyway, to combine the domains sm.checkPermission(new AuthPermission("doAsPrivileged")); return null; } }, acc); ProtectionDomain[] combined = combiner.getCombinedDomains(); return new AccessControlContext(combined); } }); } } private static class InjectingDomainCombiner implements DomainCombiner { private static Constructor domainConstructor; static { try { domainConstructor = ProtectionDomain.class.getConstructor(new Class[]{CodeSource.class, PermissionCollection.class, ClassLoader.class, Principal[].class}); } catch (Exception x) { } } private ProtectionDomain domain; private ProtectionDomain[] combined; public InjectingDomainCombiner(Subject subject) { if (domainConstructor != null) { Principal[] principals = (Principal[])subject.getPrincipals().toArray(new Principal[0]); try { domain = (ProtectionDomain)domainConstructor.newInstance(new Object[]{new CodeSource(null, (java.security.cert.Certificate[])null), null, null, principals}); } catch (Exception x) { } } if (domain == null) { // This is done for JDK 1.3 compatibility. domain = new SubjectProtectionDomain(new CodeSource(null, (java.security.cert.Certificate[])null), subject); } } public ProtectionDomain[] combine(ProtectionDomain[] current, ProtectionDomain[] assigned) { int length = current.length; ProtectionDomain[] result = null; if (assigned == null || assigned.length == 0) { result = new ProtectionDomain[length + 1]; System.arraycopy(current, 0, result, 0, length); } else { result = new ProtectionDomain[length + assigned.length + 1]; System.arraycopy(current, 0, result, 0, length); System.arraycopy(assigned, 0, result, length, assigned.length); } result[result.length - 1] = domain; this.combined = result; return result; } public ProtectionDomain[] getCombinedDomains() { return combined; } private static class SubjectProtectionDomain extends ProtectionDomain { private final Subject subject; public SubjectProtectionDomain(CodeSource codesource, Subject subject) { super(codesource, null); this.subject = subject; } public boolean implies(Permission permission) { Policy policy = (Policy)AccessController.doPrivileged(new PrivilegedAction() { public Object run() { return Policy.getPolicy(); } }); PermissionCollection permissions = policy.getPermissions(subject, getCodeSource()); return permissions.implies(permission); } } } }