1 /*
   2  * Copyright (c) 1999, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.naming.internal;
  27 
  28 import java.io.InputStream;
  29 import java.io.IOException;
  30 import java.lang.ref.WeakReference;
  31 import java.util.HashMap;
  32 import java.util.Hashtable;
  33 import java.util.Map;
  34 import java.util.Properties;
  35 import java.util.StringTokenizer;
  36 import java.util.List;
  37 import java.util.ArrayList;
  38 import java.util.WeakHashMap;
  39 
  40 import javax.naming.*;
  41 
  42 /**
  43   * The ResourceManager class facilitates the reading of JNDI resource files.
  44   *
  45   * @author Rosanna Lee
  46   * @author Scott Seligman
  47   */
  48 
  49 public final class ResourceManager {
  50 
  51     /*
  52      * Name of provider resource files (without the package-name prefix.)
  53      */
  54     private static final String PROVIDER_RESOURCE_FILE_NAME =
  55             "jndiprovider.properties";
  56 
  57     /*
  58      * Name of application resource files.
  59      */
  60     private static final String APP_RESOURCE_FILE_NAME = "jndi.properties";
  61 
  62     /*
  63      * Name of properties file in <java.home>/lib.
  64      */
  65     private static final String JRELIB_PROPERTY_FILE_NAME = "jndi.properties";
  66 
  67     /*
  68      * Internal environment property, that when set to "true", disables
  69      * application resource files lookup to prevent recursion issues
  70      * when validating signed JARs.
  71      */
  72     private static final String DISABLE_APP_RESOURCE_FILES =
  73         "com.sun.naming.disable.app.resource.files";
  74 
  75     /*
  76      * The standard JNDI properties that specify colon-separated lists.
  77      */
  78     private static final String[] listProperties = {
  79         Context.OBJECT_FACTORIES,
  80         Context.URL_PKG_PREFIXES,
  81         Context.STATE_FACTORIES,
  82         // The following shouldn't create a runtime dependence on ldap package.
  83         javax.naming.ldap.LdapContext.CONTROL_FACTORIES
  84     };
  85 
  86     private static final VersionHelper helper =
  87             VersionHelper.getVersionHelper();
  88 
  89     /*
  90      * A cache of the properties that have been constructed by
  91      * the ResourceManager.  A Hashtable from a provider resource
  92      * file is keyed on a class in the resource file's package.
  93      * One from application resource files is keyed on the thread's
  94      * context class loader.
  95      */
  96     // WeakHashMap<Class | ClassLoader, Hashtable>
  97     private static final WeakHashMap<Object, Hashtable<? super String, Object>>
  98             propertiesCache = new WeakHashMap<>(11);
  99 
 100     /*
 101      * A cache of factory objects (ObjectFactory, StateFactory, ControlFactory).
 102      *
 103      * A two-level cache keyed first on context class loader and then
 104      * on propValue.  Value is a list of class or factory objects,
 105      * weakly referenced so as not to prevent GC of the class loader.
 106      * Used in getFactories().
 107      */
 108     private static final
 109         WeakHashMap<ClassLoader, Map<String, List<NamedWeakReference<Object>>>>
 110             factoryCache = new WeakHashMap<>(11);
 111 
 112     /*
 113      * A cache of URL factory objects (ObjectFactory).
 114      *
 115      * A two-level cache keyed first on context class loader and then
 116      * on classSuffix+propValue.  Value is the factory itself (weakly
 117      * referenced so as not to prevent GC of the class loader) or
 118      * NO_FACTORY if a previous search revealed no factory.  Used in
 119      * getFactory().
 120      */
 121     private static final
 122         WeakHashMap<ClassLoader, Map<String, WeakReference<Object>>>
 123             urlFactoryCache = new WeakHashMap<>(11);
 124     private static final WeakReference<Object> NO_FACTORY =
 125             new WeakReference<>(null);
 126 
 127     // There should be no instances of this class.
 128     private ResourceManager() {
 129     }
 130 
 131 
 132     // ---------- Public methods ----------
 133 
 134     /**
 135      * Given the environment parameter passed to the initial context
 136      * constructor, returns the full environment for that initial
 137      * context (never null).  This is based on the environment
 138      * parameter, the system properties, and all application resource files.
 139      *
 140      * <p> This method will modify <tt>env</tt> and save
 141      * a reference to it.  The caller may no longer modify it.
 142      *
 143      * @param env       environment passed to initial context constructor.
 144      *                  Null indicates an empty environment.
 145      *
 146      * @throws NamingException if an error occurs while reading a
 147      *          resource file
 148      */
 149     @SuppressWarnings("unchecked")
 150     public static Hashtable<?, ?> getInitialEnvironment(Hashtable<?, ?> env)
 151             throws NamingException
 152     {
 153         String[] props = VersionHelper.PROPS;   // system properties
 154         if (env == null) {
 155             env = new Hashtable<>(11);
 156         }
 157 
 158         // Merge property values from env param, and system properties.
 159         // The first value wins: there's no concatenation of
 160         // colon-separated lists.
 161         // Read system properties by first trying System.getProperties(),
 162         // and then trying System.getProperty() if that fails.  The former
 163         // is more efficient due to fewer permission checks.
 164         //
 165         String[] jndiSysProps = helper.getJndiProperties();
 166         for (int i = 0; i < props.length; i++) {
 167             Object val = env.get(props[i]);
 168             if (val == null) {
 169                 // Read system property.
 170                 val = (jndiSysProps != null)
 171                         ? jndiSysProps[i]
 172                         : helper.getJndiProperty(i);
 173             }
 174             if (val != null) {
 175                 ((Hashtable<String, Object>)env).put(props[i], val);
 176             }
 177         }
 178 
 179         // Return without merging if application resource files lookup
 180         // is disabled.
 181         String disableAppRes = (String)env.get(DISABLE_APP_RESOURCE_FILES);
 182         if (disableAppRes != null && disableAppRes.equalsIgnoreCase("true")) {
 183             return env;
 184         }
 185 
 186         // Merge the above with the values read from all application
 187         // resource files.  Colon-separated lists are concatenated.
 188         mergeTables((Hashtable<Object, Object>)env, getApplicationResources());
 189         return env;
 190     }
 191 
 192     /**
 193       * Retrieves the property from the environment, or from the provider
 194       * resource file associated with the given context.  The environment
 195       * may in turn contain values that come from system properties,
 196       * or application resource files.
 197       *
 198       * If <tt>concat</tt> is true and both the environment and the provider
 199       * resource file contain the property, the two values are concatenated
 200       * (with a ':' separator).
 201       *
 202       * Returns null if no value is found.
 203       *
 204       * @param propName The non-null property name
 205       * @param env      The possibly null environment properties
 206       * @param ctx      The possibly null context
 207       * @param concat   True if multiple values should be concatenated
 208       * @return the property value, or null is there is none.
 209       * @throws NamingException if an error occurs while reading the provider
 210       * resource file.
 211       */
 212     public static String getProperty(String propName, Hashtable<?,?> env,
 213         Context ctx, boolean concat)
 214             throws NamingException {
 215 
 216         String val1 = (env != null) ? (String)env.get(propName) : null;
 217         if ((ctx == null) ||
 218             ((val1 != null) && !concat)) {
 219             return val1;
 220         }
 221         String val2 = (String)getProviderResource(ctx).get(propName);
 222         if (val1 == null) {
 223             return val2;
 224         } else if ((val2 == null) || !concat) {
 225             return val1;
 226         } else {
 227             return (val1 + ":" + val2);
 228         }
 229     }
 230 
 231     /**
 232      * Retrieves an enumeration of factory classes/object specified by a
 233      * property.
 234      *
 235      * The property is gotten from the environment and the provider
 236      * resource file associated with the given context and concatenated.
 237      * See getProperty(). The resulting property value is a list of class names.
 238      *<p>
 239      * This method then loads each class using the current thread's context
 240      * class loader and keeps them in a list. Any class that cannot be loaded
 241      * is ignored. The resulting list is then cached in a two-level
 242      * hash table, keyed first by the context class loader and then by
 243      * the property's value.
 244      * The next time threads of the same context class loader call this
 245      * method, they can use the cached list.
 246      *<p>
 247      * After obtaining the list either from the cache or by creating one from
 248      * the property value, this method then creates and returns a
 249      * FactoryEnumeration using the list. As the FactoryEnumeration is
 250      * traversed, the cached Class object in the list is instantiated and
 251      * replaced by an instance of the factory object itself.  Both class
 252      * objects and factories are wrapped in weak references so as not to
 253      * prevent GC of the class loader.
 254      *<p>
 255      * Note that multiple threads can be accessing the same cached list
 256      * via FactoryEnumeration, which locks the list during each next().
 257      * The size of the list will not change,
 258      * but a cached Class object might be replaced by an instantiated factory
 259      * object.
 260      *
 261      * @param propName  The non-null property name
 262      * @param env       The possibly null environment properties
 263      * @param ctx       The possibly null context
 264      * @return An enumeration of factory classes/objects; null if none.
 265      * @exception NamingException If encounter problem while reading the provider
 266      * property file.
 267      * @see javax.naming.spi.NamingManager#getObjectInstance
 268      * @see javax.naming.spi.NamingManager#getStateToBind
 269      * @see javax.naming.spi.DirectoryManager#getObjectInstance
 270      * @see javax.naming.spi.DirectoryManager#getStateToBind
 271      * @see javax.naming.ldap.ControlFactory#getControlInstance
 272      */
 273     public static FactoryEnumeration getFactories(String propName,
 274         Hashtable<?,?> env, Context ctx) throws NamingException {
 275 
 276         String facProp = getProperty(propName, env, ctx, true);
 277         if (facProp == null)
 278             return null;  // no classes specified; return null
 279 
 280         // Cache is based on context class loader and property val
 281         ClassLoader loader = helper.getContextClassLoader();
 282 
 283         Map<String, List<NamedWeakReference<Object>>> perLoaderCache = null;
 284         synchronized (factoryCache) {
 285             perLoaderCache = factoryCache.get(loader);
 286             if (perLoaderCache == null) {
 287                 perLoaderCache = new HashMap<>(11);
 288                 factoryCache.put(loader, perLoaderCache);
 289             }
 290         }
 291 
 292         synchronized (perLoaderCache) {
 293             List<NamedWeakReference<Object>> factories =
 294                     perLoaderCache.get(facProp);
 295             if (factories != null) {
 296                 // Cached list
 297                 return factories.size() == 0 ? null
 298                     : new FactoryEnumeration(factories, loader);
 299             } else {
 300                 // Populate list with classes named in facProp; skipping
 301                 // those that we cannot load
 302                 StringTokenizer parser = new StringTokenizer(facProp, ":");
 303                 factories = new ArrayList<>(5);
 304                 while (parser.hasMoreTokens()) {
 305                     try {
 306                         // System.out.println("loading");
 307                         String className = parser.nextToken();
 308                         Class<?> c = helper.loadClass(className, loader);
 309                         factories.add(new NamedWeakReference<Object>(c, className));
 310                     } catch (Exception e) {
 311                         // ignore ClassNotFoundException, IllegalArgumentException
 312                     }
 313                 }
 314                 // System.out.println("adding to cache: " + factories);
 315                 perLoaderCache.put(facProp, factories);
 316                 return new FactoryEnumeration(factories, loader);
 317             }
 318         }
 319     }
 320 
 321     /**
 322      * Retrieves a factory from a list of packages specified in a
 323      * property.
 324      *
 325      * The property is gotten from the environment and the provider
 326      * resource file associated with the given context and concatenated.
 327      * classSuffix is added to the end of this list.
 328      * See getProperty(). The resulting property value is a list of package
 329      * prefixes.
 330      *<p>
 331      * This method then constructs a list of class names by concatenating
 332      * each package prefix with classSuffix and attempts to load and
 333      * instantiate the class until one succeeds.
 334      * Any class that cannot be loaded is ignored.
 335      * The resulting object is then cached in a two-level hash table,
 336      * keyed first by the context class loader and then by the property's
 337      * value and classSuffix.
 338      * The next time threads of the same context class loader call this
 339      * method, they use the cached factory.
 340      * If no factory can be loaded, NO_FACTORY is recorded in the table
 341      * so that next time it'll return quickly.
 342      *
 343      * @param propName  The non-null property name
 344      * @param env       The possibly null environment properties
 345      * @param ctx       The possibly null context
 346      * @param classSuffix The non-null class name
 347      *                  (e.g. ".ldap.ldapURLContextFactory).
 348      * @param defaultPkgPrefix The non-null default package prefix.
 349      *        (e.g., "com.sun.jndi.url").
 350      * @return An factory object; null if none.
 351      * @exception NamingException If encounter problem while reading the provider
 352      * property file, or problem instantiating the factory.
 353      *
 354      * @see javax.naming.spi.NamingManager#getURLContext
 355      * @see javax.naming.spi.NamingManager#getURLObject
 356      */
 357     public static Object getFactory(String propName, Hashtable<?,?> env,
 358             Context ctx, String classSuffix, String defaultPkgPrefix)
 359             throws NamingException {
 360 
 361         // Merge property with provider property and supplied default
 362         String facProp = getProperty(propName, env, ctx, true);
 363         if (facProp != null)
 364             facProp += (":" + defaultPkgPrefix);
 365         else
 366             facProp = defaultPkgPrefix;
 367 
 368         // Cache factory based on context class loader, class name, and
 369         // property val
 370         ClassLoader loader = helper.getContextClassLoader();
 371         String key = classSuffix + " " + facProp;
 372 
 373         Map<String, WeakReference<Object>> perLoaderCache = null;
 374         synchronized (urlFactoryCache) {
 375             perLoaderCache = urlFactoryCache.get(loader);
 376             if (perLoaderCache == null) {
 377                 perLoaderCache = new HashMap<>(11);
 378                 urlFactoryCache.put(loader, perLoaderCache);
 379             }
 380         }
 381 
 382         synchronized (perLoaderCache) {
 383             Object factory = null;
 384 
 385             WeakReference<Object> factoryRef = perLoaderCache.get(key);
 386             if (factoryRef == NO_FACTORY) {
 387                 return null;
 388             } else if (factoryRef != null) {
 389                 factory = factoryRef.get();
 390                 if (factory != null) {  // check if weak ref has been cleared
 391                     return factory;
 392                 }
 393             }
 394 
 395             // Not cached; find first factory and cache
 396             StringTokenizer parser = new StringTokenizer(facProp, ":");
 397             String className;
 398             while (factory == null && parser.hasMoreTokens()) {
 399                 className = parser.nextToken() + classSuffix;
 400                 try {
 401                     // System.out.println("loading " + className);
 402                     factory = helper.loadClass(className, loader).newInstance();
 403                 } catch (InstantiationException e) {
 404                     NamingException ne =
 405                         new NamingException("Cannot instantiate " + className);
 406                     ne.setRootCause(e);
 407                     throw ne;
 408                 } catch (IllegalAccessException e) {
 409                     NamingException ne =
 410                         new NamingException("Cannot access " + className);
 411                     ne.setRootCause(e);
 412                     throw ne;
 413                 } catch (Exception e) {
 414                     // ignore ClassNotFoundException, IllegalArgumentException,
 415                     // etc.
 416                 }
 417             }
 418 
 419             // Cache it.
 420             perLoaderCache.put(key, (factory != null)
 421                                         ? new WeakReference<>(factory)
 422                                         : NO_FACTORY);
 423             return factory;
 424         }
 425     }
 426 
 427 
 428     // ---------- Private methods ----------
 429 
 430     /*
 431      * Returns the properties contained in the provider resource file
 432      * of an object's package.  Returns an empty hash table if the
 433      * object is null or the resource file cannot be found.  The
 434      * results are cached.
 435      *
 436      * @throws NamingException if an error occurs while reading the file.
 437      */
 438     private static Hashtable<? super String, Object>
 439         getProviderResource(Object obj)
 440             throws NamingException
 441     {
 442         if (obj == null) {
 443             return (new Hashtable<>(1));
 444         }
 445         synchronized (propertiesCache) {
 446             Class<?> c = obj.getClass();
 447 
 448             Hashtable<? super String, Object> props =
 449                     propertiesCache.get(c);
 450             if (props != null) {
 451                 return props;
 452             }
 453             props = new Properties();
 454 
 455             InputStream istream =
 456                 helper.getResourceAsStream(c, PROVIDER_RESOURCE_FILE_NAME);
 457 
 458             if (istream != null) {
 459                 try {
 460                     ((Properties)props).load(istream);
 461                 } catch (IOException e) {
 462                     NamingException ne = new ConfigurationException(
 463                             "Error reading provider resource file for " + c);
 464                     ne.setRootCause(e);
 465                     throw ne;
 466                 }
 467             }
 468             propertiesCache.put(c, props);
 469             return props;
 470         }
 471     }
 472 
 473 
 474     /*
 475      * Returns the Hashtable (never null) that results from merging
 476      * all application resource files available to this thread's
 477      * context class loader.  The properties file in <java.home>/lib
 478      * is also merged in.  The results are cached.
 479      *
 480      * SECURITY NOTES:
 481      * 1.  JNDI needs permission to read the application resource files.
 482      * 2.  Any class will be able to use JNDI to view the contents of
 483      * the application resource files in its own classpath.  Give
 484      * careful consideration to this before storing sensitive
 485      * information there.
 486      *
 487      * @throws NamingException if an error occurs while reading a resource
 488      *  file.
 489      */
 490     private static Hashtable<? super String, Object> getApplicationResources()
 491             throws NamingException {
 492 
 493         ClassLoader cl = helper.getContextClassLoader();
 494 
 495         synchronized (propertiesCache) {
 496             Hashtable<? super String, Object> result = propertiesCache.get(cl);
 497             if (result != null) {
 498                 return result;
 499             }
 500 
 501             try {
 502                 NamingEnumeration<InputStream> resources =
 503                     helper.getResources(cl, APP_RESOURCE_FILE_NAME);
 504                 try {
 505                     while (resources.hasMore()) {
 506                         Properties props = new Properties();
 507                         InputStream istream = resources.next();
 508                         try {
 509                             props.load(istream);
 510                         } finally {
 511                             istream.close();
 512                         }
 513 
 514                         if (result == null) {
 515                             result = props;
 516                         } else {
 517                             mergeTables(result, props);
 518                         }
 519                     }
 520                 } finally {
 521                     while (resources.hasMore()) {
 522                         resources.next().close();
 523                     }
 524                 }
 525 
 526                 // Merge in properties from file in <java.home>/lib.
 527                 InputStream istream =
 528                     helper.getJavaHomeLibStream(JRELIB_PROPERTY_FILE_NAME);
 529                 if (istream != null) {
 530                     try {
 531                         Properties props = new Properties();
 532                         props.load(istream);
 533 
 534                         if (result == null) {
 535                             result = props;
 536                         } else {
 537                             mergeTables(result, props);
 538                         }
 539                     } finally {
 540                         istream.close();
 541                     }
 542                 }
 543 
 544             } catch (IOException e) {
 545                 NamingException ne = new ConfigurationException(
 546                         "Error reading application resource file");
 547                 ne.setRootCause(e);
 548                 throw ne;
 549             }
 550             if (result == null) {
 551                 result = new Hashtable<>(11);
 552             }
 553             propertiesCache.put(cl, result);
 554             return result;
 555         }
 556     }
 557 
 558     /*
 559      * Merge the properties from one hash table into another.  Each
 560      * property in props2 that is not in props1 is added to props1.
 561      * For each property in both hash tables that is one of the
 562      * standard JNDI properties that specify colon-separated lists,
 563      * the values are concatenated and stored in props1.
 564      */
 565     private static void mergeTables(Hashtable<? super String, Object> props1,
 566                                     Hashtable<? super String, Object> props2) {
 567         for (Object key : props2.keySet()) {
 568             String prop = (String)key;
 569             Object val1 = props1.get(prop);
 570             if (val1 == null) {
 571                 props1.put(prop, props2.get(prop));
 572             } else if (isListProperty(prop)) {
 573                 String val2 = (String)props2.get(prop);
 574                 props1.put(prop, ((String)val1) + ":" + val2);
 575             }
 576         }
 577     }
 578 
 579     /*
 580      * Is a property one of the standard JNDI properties that specify
 581      * colon-separated lists?
 582      */
 583     private static boolean isListProperty(String prop) {
 584         prop = prop.intern();
 585         for (int i = 0; i < listProperties.length; i++) {
 586             if (prop == listProperties[i]) {
 587                 return true;
 588             }
 589         }
 590         return false;
 591     }
 592 }