DynamicSaslRegistrar.java
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 = (Stringe.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 }