001 /*
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements. See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership. The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License. You may obtain a copy of the License at
010 *
011 * http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied. See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 *
020 */
021 package org.apache.qpid.client.security;
022
023 import org.apache.qpid.util.FileUtils;
024
025 import org.slf4j.Logger;
026 import org.slf4j.LoggerFactory;
027
028 import javax.security.sasl.SaslClientFactory;
029
030 import java.io.IOException;
031 import java.io.InputStream;
032 import java.security.Security;
033 import java.util.Enumeration;
034 import java.util.Map;
035 import java.util.Properties;
036 import java.util.TreeMap;
037
038 /**
039 * DynamicSaslRegistrar provides a collection of helper methods for reading a configuration file that contains a mapping
040 * from SASL mechanism names to implementing client factory class names and registering a security provider with the
041 * Java runtime system, that uses the configured client factory implementations.
042 *
043 * <p/>The sasl configuration should be specified in a properties file, refered to by the System property
044 * "amp.dynamicsaslregistrar.properties". The format of the properties file is:
045 *
046 * <p/><pre>
047 * mechanism=fully.qualified.class.name
048 * </pre>
049 *
050 * <p/>Where mechanism is an IANA-registered mechanism name and the fully qualified class name refers to a class that
051 * implements javax.security.sasl.SaslClientFactory and provides the specified mechanism.
052 *
053 * <p><table id="crc"><caption>CRC Card</caption> <tr><th> Responsibilities <th> Collaborations <tr><td> Parse SASL
054 * mechanism properties. <tr><td> Create and register security provider for SASL mechanisms. </table>
055 */
056 public class DynamicSaslRegistrar
057 {
058 private static final Logger _logger = LoggerFactory.getLogger(DynamicSaslRegistrar.class);
059
060 /** The name of the system property that holds the name of the SASL configuration properties. */
061 private static final String FILE_PROPERTY = "amq.dynamicsaslregistrar.properties";
062
063 /** The default name of the SASL properties file resource. */
064 public static final String DEFAULT_RESOURCE_NAME = "org/apache/qpid/client/security/DynamicSaslRegistrar.properties";
065
066 /** Reads the properties file, and creates a dynamic security provider to register the SASL implementations with. */
067 public static void registerSaslProviders()
068 {
069 _logger.debug("public static void registerSaslProviders(): called");
070
071 // Open the SASL properties file, using the default name is one is not specified.
072 String filename = System.getProperty(FILE_PROPERTY);
073 InputStream is =
074 FileUtils.openFileOrDefaultResource(filename, DEFAULT_RESOURCE_NAME,
075 DynamicSaslRegistrar.class.getClassLoader());
076
077 try
078 {
079 Properties props = new Properties();
080 props.load(is);
081
082 _logger.debug("props = " + props);
083
084 Map<String, Class<? extends SaslClientFactory>> factories = parseProperties(props);
085
086 if (factories.size() > 0)
087 {
088 // Ensure we are used before the defaults
089 if (Security.insertProviderAt(new JCAProvider(factories), 1) == -1)
090 {
091 _logger.error("Unable to load custom SASL providers.");
092 }
093 else
094 {
095 _logger.info("Additional SASL providers successfully registered.");
096 }
097 }
098 else
099 {
100 _logger.warn("No additional SASL providers registered.");
101 }
102 }
103 catch (IOException e)
104 {
105 _logger.error("Error reading properties: " + e, e);
106 }
107 finally
108 {
109 if (is != null)
110 {
111 try
112 {
113 is.close();
114
115 }
116 catch (IOException e)
117 {
118 _logger.error("Unable to close properties stream: " + e, e);
119 }
120 }
121 }
122 }
123
124 /**
125 * Either attempts to open the specified filename as an input stream, or uses the default SASL configuration
126 * resource.
127 *
128 * @param filename The name of the file to get the SASL properties from, null to use the default.
129 *
130 * @return An input stream to read the dynamic SASL configuration from, or null if one could not be opened.
131 */
132 /*private static InputStream openPropertiesInputStream(String filename)
133 {
134 InputStream is = null;
135
136 // Flag to indicate whether the default resource should be used. By default this is true, so that the default
137 // is used when opening the file fails.
138 boolean useDefault = true;
139
140 // Try to open the file if one was specified.
141 if (filename != null)
142 {
143 try
144 {
145 is = new BufferedInputStream(new FileInputStream(new File(filename)));
146
147 // Clear the default flag because the file was succesfully opened.
148 useDefault = false;
149 }
150 catch (FileNotFoundException e)
151 {
152 _logger.error("Unable to read from file " + filename + ": " + e, e);
153 }
154 }
155
156 // Load the default resource if a file was not specified, or if opening the file failed.
157 if (useDefault)
158 {
159 is = CallbackHandlerRegistry.class.getResourceAsStream(DEFAULT_RESOURCE_NAME);
160 }
161
162 return is;
163 }*/
164
165 /**
166 * Parses the specified properties as a mapping from IANA registered SASL mechanism names to implementing client
167 * factories. If the client factories cannot be instantiated or do not implement SaslClientFactory then the
168 * properties refering to them are ignored.
169 *
170 * @param props The properties to scan for Sasl client factory implementations.
171 *
172 * @return A map from SASL mechanism names to implementing client factory classes.
173 *
174 * @todo Why tree map here? Do really want mechanisms in alphabetical order? Seems more likely that the declared
175 * order of the mechanisms is intended to be preserved, so that they are registered in the declared order of
176 * preference. Consider LinkedHashMap instead.
177 */
178 private static Map<String, Class<? extends SaslClientFactory>> parseProperties(Properties props)
179 {
180 Enumeration e = props.propertyNames();
181
182 TreeMap<String, Class<? extends SaslClientFactory>> factoriesToRegister =
183 new TreeMap<String, Class<? extends SaslClientFactory>>();
184
185 while (e.hasMoreElements())
186 {
187 String mechanism = (String) e.nextElement();
188 String className = props.getProperty(mechanism);
189 try
190 {
191 Class<?> clazz = Class.forName(className);
192 if (!(SaslClientFactory.class.isAssignableFrom(clazz)))
193 {
194 _logger.error("Class " + clazz + " does not implement " + SaslClientFactory.class + " - skipping");
195
196 continue;
197 }
198
199 _logger.debug("Registering class "+ clazz.getName() +" for mechanism "+mechanism);
200 factoriesToRegister.put(mechanism, (Class<? extends SaslClientFactory>) clazz);
201 }
202 catch (Exception ex)
203 {
204 _logger.error("Error instantiating SaslClientFactory calss " + className + " - skipping");
205 }
206 }
207
208 return factoriesToRegister;
209 }
210 }
|