1 /*
   2  * Copyright (c) 2003, 2013, 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 sun.security.jca;
  27 
  28 import java.io.File;
  29 import java.lang.reflect.*;
  30 
  31 import java.security.*;
  32 
  33 import sun.security.util.PropertyExpander;
  34 
  35 /**
  36  * Class representing a configured provider. Encapsulates configuration
  37  * (className plus optional argument), the provider loading logic, and
  38  * the loaded Provider object itself.
  39  *
  40  * @author  Andreas Sterbenz
  41  * @since   1.5
  42  */
  43 final class ProviderConfig {
  44 
  45     private final static sun.security.util.Debug debug =
  46         sun.security.util.Debug.getInstance("jca", "ProviderConfig");
  47 
  48     // classname of the SunPKCS11-Solaris provider
  49     private static final String P11_SOL_NAME =
  50         "sun.security.pkcs11.SunPKCS11";
  51 
  52     // config file argument of the SunPKCS11-Solaris provider
  53     private static final String P11_SOL_ARG  =
  54         "${java.home}/lib/security/sunpkcs11-solaris.cfg";
  55 
  56     // maximum number of times to try loading a provider before giving up
  57     private final static int MAX_LOAD_TRIES = 30;
  58 
  59     // parameters for the Provider(String) constructor,
  60     // use by doLoadProvider()
  61     private final static Class<?>[] CL_STRING = { String.class };
  62 
  63     // name of the provider class
  64     private final String className;
  65 
  66     // argument to the provider constructor,
  67     // empty string indicates no-arg constructor
  68     private final String argument;
  69 
  70     // number of times we have already tried to load this provider
  71     private int tries;
  72 
  73     // Provider object, if loaded
  74     private volatile Provider provider;
  75 
  76     // flag indicating if we are currently trying to load the provider
  77     // used to detect recursion
  78     private boolean isLoading;
  79 
  80     ProviderConfig(String className, String argument) {
  81         if (className.equals(P11_SOL_NAME) && argument.equals(P11_SOL_ARG)) {
  82             checkSunPKCS11Solaris();
  83         }
  84         this.className = className;
  85         this.argument = expand(argument);
  86     }
  87 
  88     ProviderConfig(String className) {
  89         this(className, "");
  90     }
  91 
  92     ProviderConfig(Provider provider) {
  93         this.className = provider.getClass().getName();
  94         this.argument = "";
  95         this.provider = provider;
  96     }
  97 
  98     // check if we should try to load the SunPKCS11-Solaris provider
  99     // avoid if not available (pre Solaris 10) to reduce startup time
 100     // or if disabled via system property
 101     private void checkSunPKCS11Solaris() {
 102         Boolean o = AccessController.doPrivileged(
 103                                 new PrivilegedAction<Boolean>() {
 104             public Boolean run() {
 105                 File file = new File("/usr/lib/libpkcs11.so");
 106                 if (file.exists() == false) {
 107                     return Boolean.FALSE;
 108                 }
 109                 if ("false".equalsIgnoreCase(System.getProperty
 110                         ("sun.security.pkcs11.enable-solaris"))) {
 111                     return Boolean.FALSE;
 112                 }
 113                 return Boolean.TRUE;
 114             }
 115         });
 116         if (o == Boolean.FALSE) {
 117             tries = MAX_LOAD_TRIES;
 118         }
 119     }
 120 
 121     private boolean hasArgument() {
 122         return argument.length() != 0;
 123     }
 124 
 125     // should we try to load this provider?
 126     private boolean shouldLoad() {
 127         return (tries < MAX_LOAD_TRIES);
 128     }
 129 
 130     // do not try to load this provider again
 131     private void disableLoad() {
 132         tries = MAX_LOAD_TRIES;
 133     }
 134 
 135     boolean isLoaded() {
 136         return (provider != null);
 137     }
 138 
 139     public boolean equals(Object obj) {
 140         if (this == obj) {
 141             return true;
 142         }
 143         if (obj instanceof ProviderConfig == false) {
 144             return false;
 145         }
 146         ProviderConfig other = (ProviderConfig)obj;
 147         return this.className.equals(other.className)
 148             && this.argument.equals(other.argument);
 149     }
 150 
 151     public int hashCode() {
 152         return className.hashCode() + argument.hashCode();
 153     }
 154 
 155     public String toString() {
 156         if (hasArgument()) {
 157             return className + "('" + argument + "')";
 158         } else {
 159             return className;
 160         }
 161     }
 162 
 163     /**
 164      * Get the provider object. Loads the provider if it is not already loaded.
 165      */
 166     synchronized Provider getProvider() {
 167         // volatile variable load
 168         Provider p = provider;
 169         if (p != null) {
 170             return p;
 171         }
 172         if (shouldLoad() == false) {
 173             return null;
 174         }
 175         if (isLoading) {
 176             // because this method is synchronized, this can only
 177             // happen if there is recursion.
 178             if (debug != null) {
 179                 debug.println("Recursion loading provider: " + this);
 180                 new Exception("Call trace").printStackTrace();
 181             }
 182             return null;
 183         }
 184         try {
 185             isLoading = true;
 186             tries++;
 187             p = doLoadProvider();
 188         } finally {
 189             isLoading = false;
 190         }
 191         provider = p;
 192         return p;
 193     }
 194 
 195     /**
 196      * Load and instantiate the Provider described by this class.
 197      *
 198      * NOTE use of doPrivileged().
 199      *
 200      * @return null if the Provider could not be loaded
 201      *
 202      * @throws ProviderException if executing the Provider's constructor
 203      * throws a ProviderException. All other Exceptions are ignored.
 204      */
 205     private Provider doLoadProvider() {
 206         return AccessController.doPrivileged(new PrivilegedAction<Provider>() {
 207             public Provider run() {
 208                 if (debug != null) {
 209                     debug.println("Loading provider: " + ProviderConfig.this);
 210                 }
 211                 try {
 212                     ClassLoader cl = ClassLoader.getSystemClassLoader();
 213                     Class<?> provClass;
 214                     if (cl != null) {
 215                         provClass = cl.loadClass(className);
 216                     } else {
 217                         provClass = Class.forName(className);
 218                     }
 219                     Object obj;
 220                     if (hasArgument() == false) {
 221                         obj = provClass.newInstance();
 222                     } else {
 223                         Constructor<?> cons = provClass.getConstructor(CL_STRING);
 224                         obj = cons.newInstance(argument);
 225                     }
 226                     if (obj instanceof Provider) {
 227                         if (debug != null) {
 228                             debug.println("Loaded provider " + obj);
 229                         }
 230                         return (Provider)obj;
 231                     } else {
 232                         if (debug != null) {
 233                             debug.println(className + " is not a provider");
 234                         }
 235                         disableLoad();
 236                         return null;
 237                     }
 238                 } catch (Exception e) {
 239                     Throwable t;
 240                     if (e instanceof InvocationTargetException) {
 241                         t = ((InvocationTargetException)e).getCause();
 242                     } else {
 243                         t = e;
 244                     }
 245                     if (debug != null) {
 246                         debug.println("Error loading provider " + ProviderConfig.this);
 247                         t.printStackTrace();
 248                     }
 249                     // provider indicates fatal error, pass through exception
 250                     if (t instanceof ProviderException) {
 251                         throw (ProviderException)t;
 252                     }
 253                     // provider indicates that loading should not be retried
 254                     if (t instanceof UnsupportedOperationException) {
 255                         disableLoad();
 256                     }
 257                     return null;
 258                 } catch (ExceptionInInitializerError err) {
 259                     // no sufficient permission to initialize provider class
 260                     if (debug != null) {
 261                         debug.println("Error loading provider " + ProviderConfig.this);
 262                         err.printStackTrace();
 263                     }
 264                     disableLoad();
 265                     return null;
 266                 }
 267             }
 268         });
 269     }
 270 
 271     /**
 272      * Perform property expansion of the provider value.
 273      *
 274      * NOTE use of doPrivileged().
 275      */
 276     private static String expand(final String value) {
 277         // shortcut if value does not contain any properties
 278         if (value.contains("${") == false) {
 279             return value;
 280         }
 281         return AccessController.doPrivileged(new PrivilegedAction<String>() {
 282             public String run() {
 283                 try {
 284                     return PropertyExpander.expand(value);
 285                 } catch (GeneralSecurityException e) {
 286                     throw new ProviderException(e);
 287                 }
 288             }
 289         });
 290     }
 291 
 292 }