/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.internal.api;
import com.sun.enterprise.util.i18n.StringManager;
import java.io.File;
import com.sun.enterprise.util.i18n.StringManagerBase;
import com.sun.logging.LogDomains;
import org.glassfish.security.common.MasterPassword;
import com.sun.enterprise.security.store.PasswordAdapter;
import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.logging.Logger;
import java.util.logging.Level;
/**
* The purpose of this class is to expand paths that contain embedded
* system properties of the form ${property-name}. The result must be
* an absolute path, or messages are logged. Here are some examples:
*
* ${com.sun.aas.installRoot}/config/domain.xml
* /foo/${config}/domain.xml
* /foo/${config}/${domain-name}
*
* This class is used to map paths containing system properties in
* domain.xml and used so that absolute paths (which are installation
* directory specific) are not present, making domain.xml portable
* in an SE/EE environment across many machines (with different
* installation directories).
*/
public class RelativePathResolver {
private static Logger _logger = null;
private static RelativePathResolver _instance = null;
private PasswordAdapter pwdAdapter = null;
private static final String ALIAS_TOKEN = "ALIAS";
private static final String ALIAS_DELIMITER = "=";
private static MasterPassword masterPasswordHelper = null;
static{
masterPasswordHelper = Globals.getDefaultHabitat().getByContract(MasterPassword.class);
}
protected synchronized static Logger getLogger() {
if (_logger == null) {
_logger = LogDomains.getLogger(RelativePathResolver.class, LogDomains.UTIL_LOGGER);
}
return _logger;
}
private synchronized static RelativePathResolver getInstance()
{
if (_instance == null) {
_instance = new RelativePathResolver();
}
return _instance;
}
public static String unresolvePath(String path, String[] propNames)
{
return getInstance().unresolve(path, propNames);
}
public static String resolvePath(String path)
{
return getInstance().resolve(path);
}
public RelativePathResolver()
{
}
/**
* unresolvePath will replace the first occurrence of the value of the given
* system properties with ${propName} in the given path
**/
public String unresolve(String path, String[] propNames) {
if (path != null) {
int startIdx;
String propVal;
//All paths returned will contain / as the separator. The
//assumption is that the File class can convert this to an OS
//dependent path separator (e.g. \\ on windows).
path = path.replace(File.separatorChar, '/');
for (int i = 0; i < propNames.length; i++) {
propVal = getPropertyValue(propNames[i], true);
if (propVal != null) {
//All paths returned will contain / as the separator. This will allow
//all comparison to be done using / as the separator
propVal = propVal.replace(File.separatorChar, '/');
startIdx = path.indexOf(propVal);
if (startIdx >= 0) {
path = path.substring(0, startIdx) +
"${" + propNames[i] + "}" +
path.substring(startIdx + propVal.length());
}
} else {
getLogger().log(Level.SEVERE,
"enterprise_util.path_unresolver_missing_property",
new Object[] {propNames[i], path});
}
}
}
return path;
}
/**
* You would like to think that we could just log and continue (without throwing
a RuntimeException; however, unfortunately anything logged by the logger in the
* launcher (PELaucnhFilter) does not appear in server.log, so for now, this
* will be considered a fatal error.
*/
protected void fatalError(String message, String path) {
getLogger().log(Level.SEVERE, message, new Object[] {path});
StringManagerBase sm = StringManagerBase.getStringManager(getLogger().getResourceBundleName(),
getClass().getClassLoader());
throw new RuntimeException(sm.getString(message, path));
}
private void appendChar (char c, StringBuffer propName, StringBuffer result)
{
if (propName == null) {
result.append(c);
} else {
propName.append(c);
}
}
/**
* check if a given property name matches AS alias pattern ${ALIAS=aliasname}.
* if so, return the aliasname, otherwise return null.
* @param propName The property name to resolve. ex. ${ALIAS=aliasname}.
* @return The aliasname or null.
*/
static public String getAlias(String propName)
{
String aliasName=null;
String starter = "${" + ALIAS_TOKEN + "="; //no space is allowed in starter
String ender = "}";
propName = propName.trim();
if (propName.startsWith(starter) && propName.endsWith(ender) ) {
propName = propName.substring(starter.length() );
int lastIdx = propName.length() - 1;
if (lastIdx > 1) {
propName = propName.substring(0,lastIdx);
if (propName!=null)
aliasName = propName.trim();
}
}
return aliasName;
}
/**
* Resolves the given property by returning its value as either
* 1) a system property of the form ${system-property-name}
* 2) a password alias property of the form ${ALIAS=aliasname}. Here the alias name
* is mapped to a password.
* @param propName The property name to resolve
* @return The resolved value of the property or null.
*/
protected String getPropertyValue(String propName, boolean bIncludingEnvironmentVariables)
{
if(!bIncludingEnvironmentVariables)
return null;
// Try finding the property as a system property
String result = System.getProperty(propName);
if (result == null) {
//If not found as a system property, the see if it is a password alias.
int idx1 = propName.indexOf(ALIAS_TOKEN);
if (idx1 >= 0) {
int idx2 = propName.indexOf(ALIAS_DELIMITER, ALIAS_TOKEN.length());
if (idx2 > 0) {
String aliasName = propName.substring(idx2 + 1).trim();
//System.err.println("aliasName " + aliasName);
try {
if (pwdAdapter==null) {
pwdAdapter = masterPasswordHelper.getMasterPasswordAdapter();
}
result = pwdAdapter.getPasswordForAlias(aliasName);
//System.err.println("alias password " + result);
} catch (Exception ex) {
getLogger().log(Level.WARNING, "enterprise_util.path_resolver_alias_exception",
new Object[] {ex, aliasName, propName});
getLogger().log(Level.FINE, "enterprise_util.path_resolver_alias_exception",
ex);
}
}
}
}
return result;
}
public String resolve(String path) {
return resolve(path, true);
}
/**
* Replace any system properties of the form ${property} in the given path. Note
* any mismatched delimiters (e.g. ${property/${property2} is considered a fatal
* error and for now causes a fatal RuntimeException to be thrown.
*/
public String resolve(String path, boolean bIncludingEnvironmentVariables) {
if (path == null) {
return path;
}
//Now parse through the given string one character at a time looking for the
//starting delimiter "${". Occurrences of "$" or "{" are valid characters;
//however once an occurrence of "${" is found, then "}" becomes a closing
//delimiter.
int size = path.length();
StringBuffer result = new StringBuffer(size);
StringBuffer propName = null;
String propVal;
//keep track of whether we have found at least one occurrence of "${". The
//significance is that "}" is parsed as a terminating character.
boolean foundOne = false;
char c;
for (int i = 0; i < size; i++) {
c = path.charAt(i);
switch(c) {
case '$': {
if (i < size - 1 && path.charAt(i + 1) == '{') {
//found "${"
foundOne = true;
i++;
if (propName == null) { // start parsing a new property Name
propName = new StringBuffer();
break;
} else { // previous property not terminated missing }
fatalError(
"enterprise_util.path_resolver_missing_closing_delim",
path);
return path; //can't happen since fatalError throws RuntimeException
}
} else {
appendChar(c, propName, result);
}
break;
} case '}': {
if (foundOne) { // we have found at least one occurrence of ${
if (propName != null) {
propVal = getPropertyValue(propName.toString(), bIncludingEnvironmentVariables);
if (propVal != null) {
//Note: when elaborating a system property, we always convert \\ to / to ensure that
//paths created on windows are compatible with unix filesystems.
result.append(propVal.replace(File.separatorChar, '/'));
} else {
//NOTE: We cannot ensure that system properties will always
//be defined and so this is an expected case. Consider
//a property named ${http-listener-port}. This property
//may be defined at the server or config level and set only
//when that server instance starts up. The property may not
//be set in the DAS.
result.append("${" + propName + "}");
}
propName = null;
} else { //no matching starting delimiter found ${
fatalError(
"enterprise_util.path_resolver_missing_starting_delim",
path);
return path; //can't happen since fatalError throws RuntimeException
}
} else {
appendChar(c, propName, result);
}
break;
} default : {
appendChar(c, propName, result);
break;
}
}
}
if (propName != null) { // missing final }
fatalError(
"enterprise_util.path_resolver_missing_closing_delim",
path);
return path; //can't happen
}
return result.toString();
}
/**
* checks if string does not consist of unresolvable values
*/
public boolean isResolvable(String path, boolean bIncludingEnvironmentVariables) {
String resolved = resolve(path, bIncludingEnvironmentVariables);
return (resolved.indexOf("${")<0);
}
public static void main(String[] args) {
if (args[0].equalsIgnoreCase("unresolve")) {
for (int i = 2; i < args.length; i++) {
String result = unresolvePath(args[i], new String[] {args[1]});
System.out.println(args[i] + " " + result + " " + resolvePath(result));
}
} else {
for (int i = 0; i < args.length; i++) {
System.out.println(args[i] + " " + resolvePath(args[i]));
}
}
}
/** Returns the actual password from the domain-wide safe password store,
* if the given password is aliased. An aliased String is of the form
* ${ALIAS=aliasname} where the actual password is stored in given alias name.
* Following are the returned values:
* <ul>
* <li> Returns a null if given String is null. </li>
* <li> Retuns the given String if it is not in the alias form. </li>
* <li> Returns the real password from store if the given String is
* of the alias form and the alias has been created by the
* administrator. If the alias is not defined in the store,
* an IllegalArgumentException is thrown with appropriate
* message. </li>
* </ul>
* @param at is the aliased token of the form "${ALIAS=string}"
* @return a String representing the actual password
* @throws IllegalArgumentException if the alias is not defined
* @throws KeyStoreException CertificateException IOException NoSuchAlgorithmException
* UnrecoverableKeyException if there is an error is opening or
* processing the password store
*/
public static String getRealPasswordFromAlias(final String at) throws
KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException,
UnrecoverableKeyException {
try {
if (at == null || RelativePathResolver.getAlias(at) == null) {
return ( at );
}
} catch (final Exception e) { //underlying code is unsafe!
return (at);
}
final String an = RelativePathResolver.getAlias(at);
final PasswordAdapter pa = masterPasswordHelper.getMasterPasswordAdapter(); // use default password store
final boolean exists = pa.aliasExists(an);
if (!exists) {
final StringManager lsm = StringManager.getManager(RelativePathResolver.class);
final String msg = lsm.getString("no_such_alias", an, at);
throw new IllegalArgumentException(msg);
}
final String real = pa.getPasswordForAlias(an);
return ( real );
}
}