/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.java.sip.communicator.plugin.provisioning;
import java.awt.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.List;
import java.util.regex.*;
import javax.swing.*;
import net.java.sip.communicator.service.httputil.*;
import net.java.sip.communicator.service.provisioning.*;
import net.java.sip.communicator.util.*;
import net.java.sip.communicator.util.Logger;
import net.java.sip.communicator.plugin.desktoputil.*;
import org.apache.http.*;
import org.jitsi.service.configuration.*;
import org.jitsi.service.resources.*;
import org.jitsi.util.*;
import org.osgi.framework.*;
// disambiguation
/**
* Provisioning service.
*
* @author Sebastien Vincent
*/
public class ProvisioningServiceImpl
implements ProvisioningService
{
/**
* Logger of this class
*/
private static final Logger logger
= Logger.getLogger(ProvisioningServiceImpl.class);
/**
* Name of the UUID property.
*/
public static final String PROVISIONING_UUID_PROP
= "net.java.sip.communicator.UUID";
/**
* Name of the provisioning URL in the configuration service.
*/
private static final String PROPERTY_PROVISIONING_URL
= "net.java.sip.communicator.plugin.provisioning.URL";
/**
* Name of the provisioning username in the configuration service
* authentication).
*/
static final String PROPERTY_PROVISIONING_USERNAME
= "net.java.sip.communicator.plugin.provisioning.auth.USERNAME";
/**
* Name of the provisioning password in the configuration service (HTTP
* authentication).
*/
static final String PROPERTY_PROVISIONING_PASSWORD
= "net.java.sip.communicator.plugin.provisioning.auth";
/**
* Name of the property that contains the provisioning method (i.e. DHCP,
* DNS, manual, ...).
*/
private static final String PROVISIONING_METHOD_PROP
= "net.java.sip.communicator.plugin.provisioning.METHOD";
/**
* Name of the property, whether provisioning is mandatory.
*/
private static final String PROPERTY_PROVISIONING_MANDATORY
= "net.java.sip.communicator.plugin.provisioning.MANDATORY";
/**
* Name of the property that contains enforce prefix list (separated by
* pipe) for the provisioning. The retrieved configuration properties will
* be checked against these prefixes to avoid having incorrect content in
* the configuration file (such as HTML content resulting of HTTP error).
*/
private static final String PROVISIONING_ALLOW_PREFIX_PROP
= "provisioning.ALLOW_PREFIX";
/**
* Name of the enforce prefix property.
*/
private static final String PROVISIONING_ENFORCE_PREFIX_PROP
= "provisioning.ENFORCE_PREFIX";
/**
* List of allowed configuration prefixes.
*/
private List<String> allowedPrefixes = new ArrayList<String>();
/**
* Authentication username.
*/
private static String provUsername = null;
/**
* Authentication password.
*/
private static String provPassword = null;
/**
* Prefix that can be used to indicate a property that will be
* set as a system property.
*/
private static final String SYSTEM_PROP_PREFIX = "${system}.";
/**
* Constructor.
*/
public ProvisioningServiceImpl()
{
// check if UUID is already configured
String uuid = (String)ProvisioningActivator.getConfigurationService().
getProperty(PROVISIONING_UUID_PROP);
if(uuid == null || uuid.equals(""))
{
uuid = UUID.randomUUID().toString();
ProvisioningActivator.getConfigurationService().setProperty(
PROVISIONING_UUID_PROP, uuid);
}
}
/**
* Starts provisioning.
*
* @param url provisioning URL
*/
void start(String url)
{
if(url == null)
{
/* try to see if provisioning URL is stored in properties */
url = getProvisioningUri();
}
if(!StringUtils.isNullOrEmpty(url))
{
InputStream data = retrieveConfigurationFile(url);
if(data != null)
{
/* store the provisioning URL in local configuration in case
* the provisioning discovery failed (DHCP/DNS unavailable, ...)
*/
ProvisioningActivator.getConfigurationService().setProperty(
PROPERTY_PROVISIONING_URL, url);
updateConfiguration(data);
}
}
}
/**
* Indicates if the provisioning has been enabled.
*
* @return <tt>true</tt> if the provisioning is enabled, <tt>false</tt> -
* otherwise
*/
public String getProvisioningMethod()
{
String provMethod
= ProvisioningActivator.getConfigurationService().getString(
PROVISIONING_METHOD_PROP);
if (provMethod == null || provMethod.length() <= 0)
{
provMethod = ProvisioningActivator.getResourceService().
getSettingsString(
"plugin.provisioning.DEFAULT_PROVISIONING_METHOD");
if (provMethod != null && provMethod.length() > 0)
setProvisioningMethod(provMethod);
}
return provMethod;
}
/**
* Enables the provisioning with the given method. If the provisioningMethod
* is null disables the provisioning.
*
* @param provisioningMethod the provisioning method
*/
public void setProvisioningMethod(String provisioningMethod)
{
ProvisioningActivator.getConfigurationService().setProperty(
PROVISIONING_METHOD_PROP, provisioningMethod);
}
/**
* Returns the provisioning URI.
*
* @return the provisioning URI
*/
public String getProvisioningUri()
{
String provUri
= ProvisioningActivator.getConfigurationService().getString(
PROPERTY_PROVISIONING_URL);
if (provUri == null || provUri.length() <= 0)
{
provUri = ProvisioningActivator.getResourceService().
getSettingsString(
"plugin.provisioning.DEFAULT_PROVISIONING_URI");
if (provUri != null && provUri.length() > 0)
setProvisioningUri(provUri);
}
return provUri;
}
/**
* Sets the provisioning URI.
*
* @param uri the provisioning URI to set
*/
public void setProvisioningUri(String uri)
{
ProvisioningActivator.getConfigurationService().setProperty(
PROPERTY_PROVISIONING_URL, uri);
}
/**
* Returns provisioning username.
*
* @return provisioning username
*/
public String getProvisioningUsername()
{
return provUsername;
}
/**
* Returns provisioning password.
*
* @return provisioning password
*/
public String getProvisioningPassword()
{
return provPassword;
}
/**
* Retrieve configuration file from provisioning URL.
* This method is blocking until configuration file is retrieved from the
* network or if an exception happen
*
* @param url provisioning URL
* @return Stream of provisioning data
*/
private InputStream retrieveConfigurationFile(String url)
{
return retrieveConfigurationFile(url, null);
}
/**
* Retrieve configuration file from provisioning URL.
* This method is blocking until configuration file is retrieved from the
* network or if an exception happen
*
* @param url provisioning URL
* @param parameters the already filled parameters if any.
* @return Stream of provisioning data
*/
private InputStream retrieveConfigurationFile(String url,
List<NameValuePair> parameters)
{
try
{
String arg = null;
String args[] = null;
URL u = new URL(url);
InetAddress ipaddr =
ProvisioningActivator.getNetworkAddressManagerService().
getLocalHost(InetAddress.getByName(u.getHost()));
// Get any system environment identified by ${env.xyz}
Pattern p = Pattern.compile("\\$\\{env\\.([^\\}]*)\\}");
Matcher m = p.matcher(url);
StringBuffer sb = new StringBuffer();
while(m.find())
{
String value = System.getenv(m.group(1));
if(value != null)
{
m.appendReplacement(sb, Matcher.quoteReplacement(value));
}
}
m.appendTail(sb);
url = sb.toString();
// Get any system property variable identified by ${system.xyz}
p = Pattern.compile("\\$\\{system\\.([^\\}]*)\\}");
m = p.matcher(url);
sb = new StringBuffer();
while(m.find())
{
String value = System.getProperty(m.group(1));
if(value != null)
{
m.appendReplacement(sb, Matcher.quoteReplacement(value));
}
}
m.appendTail(sb);
url = sb.toString();
if(url.indexOf("${home.location}") != -1)
{
url = url.replace("${home.location}",
ProvisioningActivator.getConfigurationService()
.getScHomeDirLocation());
}
if(url.indexOf("${home.name}") != -1)
{
url = url.replace("${home.name}",
ProvisioningActivator.getConfigurationService()
.getScHomeDirName());
}
if(url.indexOf("${uuid}") != -1)
{
url = url.replace("${uuid}",
(String)ProvisioningActivator.getConfigurationService()
.getProperty(PROVISIONING_UUID_PROP));
}
if(url.indexOf("${osname}") != -1)
{
url = url.replace("${osname}", System.getProperty("os.name"));
}
if(url.indexOf("${arch}") != -1)
{
url = url.replace("${arch}", System.getProperty("os.arch"));
}
if(url.indexOf("${resx}") != -1 || url.indexOf("${resy}") != -1)
{
Rectangle screen = ScreenInformation.getScreenBounds();
if(url.indexOf("${resx}") != -1)
{
url = url.replace("${resx}", String.valueOf(screen.width));
}
if(url.indexOf("${resy}") != -1)
{
url = url.replace("${resy}", String.valueOf(screen.height));
}
}
if(url.indexOf("${build}") != -1)
{
url = url.replace("${build}",
System.getProperty("sip-communicator.version"));
}
if(url.indexOf("${locale}") != -1)
{
String locale =
ProvisioningActivator.getConfigurationService().getString(
ResourceManagementService.DEFAULT_LOCALE_CONFIG);
if(locale == null)
locale = "";
url = url.replace("${locale}", locale);
}
if(url.indexOf("${ipaddr}") != -1)
{
url = url.replace("${ipaddr}", ipaddr.getHostAddress());
}
if(url.indexOf("${hostname}") != -1)
{
String name;
if(OSUtils.IS_WINDOWS)
{
// avoid reverse DNS lookup
name = System.getenv("COMPUTERNAME");
}
else
{
name = ipaddr.getHostName();
}
url = url.replace("${hostname}", name);
}
if(url.indexOf("${hwaddr}") != -1)
{
if(ipaddr != null)
{
/* find the hardware address of the interface
* that has this IP address
*/
Enumeration<NetworkInterface> en =
NetworkInterface.getNetworkInterfaces();
while(en.hasMoreElements())
{
NetworkInterface iface = en.nextElement();
Enumeration<InetAddress> enInet =
iface.getInetAddresses();
while(enInet.hasMoreElements())
{
InetAddress inet = enInet.nextElement();
if(inet.equals(ipaddr))
{
byte hw[] =
ProvisioningActivator.
getNetworkAddressManagerService().
getHardwareAddress(iface);
if(hw == null || hw.length == 0)
continue;
StringBuffer buf =
new StringBuffer();
for(byte h : hw)
{
int hi = h >= 0 ? h : h + 256;
String t = new String(
(hi <= 0xf) ? "0" : "");
t += Integer.toHexString(hi);
buf.append(t);
buf.append(":");
}
buf.deleteCharAt(buf.length() - 1);
url = url.replace("${hwaddr}",
buf.toString());
break;
}
}
}
}
}
if(url.contains("?"))
{
/* do not handle URL of type http://domain/index.php? (no
* parameters)
*/
if((url.indexOf('?') + 1) != url.length())
{
arg = url.substring(url.indexOf('?') + 1);
args = arg.split("&");
}
url = url.substring(0, url.indexOf('?'));
}
ArrayList<String> paramNames = null;
ArrayList<String> paramValues = null;
int usernameIx = -1;
int passwordIx = -1;
if(args != null && args.length > 0)
{
paramNames = new ArrayList<String>(args.length);
paramValues = new ArrayList<String>(args.length);
String usernameParam = "${username}";
String passwordParam = "${password}";
for(int i = 0; i < args.length; i++)
{
String s = args[i];
int equalsIndex = s.indexOf("=");
String currentParamName = null;
if (equalsIndex > -1)
{
currentParamName = s.substring(0, equalsIndex);
}
// pre loaded value we will reuse.
String preloadedParamValue =
getParamValue(parameters, currentParamName);
// If we find the username or password parameter at this
// stage we replace it with an empty string.
// or if we have an already filled value we will reuse it.
if(s.indexOf(usernameParam) != -1)
{
if(preloadedParamValue != null)
{
s = s.replace(usernameParam, preloadedParamValue);
}
else
s = s.replace(usernameParam, "");
usernameIx = paramNames.size();
}
else if(s.indexOf(passwordParam) != -1)
{
if(preloadedParamValue != null)
{
s = s.replace(passwordParam, preloadedParamValue);
}
else
s = s.replace(passwordParam, "");
passwordIx = paramNames.size();
}
if (equalsIndex > -1)
{
paramNames.add(currentParamName);
paramValues.add(s.substring(equalsIndex + 1));
}
else
{
if(logger.isInfoEnabled())
{
logger.info(
"Invalid provisioning request parameter: \""
+ s + "\", is replaced by \"" + s + "=\"");
}
paramNames.add(s);
paramValues.add("");
}
}
}
HttpUtils.HTTPResponseResult res = null;
Throwable errorWhileProvisioning = null;
try
{
res =
HttpUtils.postForm(
url,
PROPERTY_PROVISIONING_USERNAME,
PROPERTY_PROVISIONING_PASSWORD,
paramNames,
paramValues,
usernameIx,
passwordIx,
new HttpUtils.RedirectHandler()
{
@Override
public boolean handleRedirect(
String location,
List<NameValuePair> parameters)
{
if(!hasParams(location))
return false;
// if we have parameters proceed
retrieveConfigurationFile(location, parameters);
return true;
}
@Override
public boolean hasParams(String location)
{
return location.contains("${");
}
});
}
catch(Throwable t)
{
logger.error("Error posting form", t);
errorWhileProvisioning = t;
}
// if there was an error in retrieving stop
if(res == null)
{
// if canceled, lets check whether provisioning is
// mandatory
if(ProvisioningActivator.getConfigurationService().getBoolean(
PROPERTY_PROVISIONING_MANDATORY, false))
{
String errorMsg;
if(errorWhileProvisioning != null)
errorMsg = errorWhileProvisioning.getLocalizedMessage();
else
errorMsg = "";
ErrorDialog ed = new ErrorDialog(
null,
ProvisioningActivator.getResourceService()
.getI18NString("plugin.provisioning.PROV_FAILED"),
ProvisioningActivator.getResourceService()
.getI18NString("plugin.provisioning.PROV_FAILED_MSG",
new String[]{errorMsg}),
errorWhileProvisioning);
ed.setModal(true);
ed.showDialog();
// as shutdown service is not started and other bundles
// are scheduled to start, stop all of them
{
for(Bundle b : ProvisioningActivator.bundleContext
.getBundles())
{
try
{
// skip our Bundle avoiding stopping us while
// starting and NPE in felix
if(ProvisioningActivator.bundleContext
.equals(b.getBundleContext()))
{
continue;
}
b.stop();
}
catch (BundleException ex)
{
logger.error(
"Failed to being gentle stop " +
b.getLocation(), ex);
}
}
}
}
// stop processing
return null;
}
String userPass[] = res.getCredentials();
if(userPass[0] != null && userPass[1] != null)
{
provUsername = userPass[0];
provPassword = userPass[1];
}
InputStream in = res.getContent();
// Skips ProgressMonitorInputStream wrapper on Android
if(!OSUtils.IS_ANDROID)
{
// Chain a ProgressMonitorInputStream to the
// URLConnection's InputStream
final ProgressMonitorInputStream pin;
pin = new ProgressMonitorInputStream(null, u.toString(), in);
// Set the maximum value of the ProgressMonitor
ProgressMonitor pm = pin.getProgressMonitor();
pm.setMaximum((int)res.getContentLength());
// Uses ProgressMonitorInputStream if available
return pin;
}
return in;
}
catch (Exception e)
{
if (logger.isInfoEnabled())
logger.info("Error retrieving provisioning file!", e);
return null;
}
}
/**
* Search param value for the supplied name.
* @param parameters the parameters can be null.
* @param paramName the name to search.
* @return the corresponding parameter value.
*/
private static String getParamValue(List<NameValuePair> parameters,
String paramName)
{
if(parameters == null || paramName == null)
return null;
for(NameValuePair nv : parameters)
{
if(nv.getName().equals(paramName))
return nv.getValue();
}
return null;
}
/**
* Update configuration with properties retrieved from provisioning URL.
*
* @param data Provisioning data
*/
private void updateConfiguration(final InputStream data)
{
Properties fileProps = new OrderedProperties();
InputStream in = null;
try
{
in = new BufferedInputStream(data);
fileProps.load(in);
Iterator<Map.Entry<Object, Object> > it
= fileProps.entrySet().iterator();
while(it.hasNext())
{
Map.Entry<Object, Object> entry = it.next();
String key = (String)entry.getKey();
Object value = entry.getValue();
// skip empty keys, prevent them going into the configuration
if(key.trim().length() == 0)
continue;
if(key.equals(PROVISIONING_ALLOW_PREFIX_PROP))
{
String prefixes[] = ((String)value).split("\\|");
/* updates allowed prefixes list */
for(String s : prefixes)
{
allowedPrefixes.add(s);
}
continue;
}
else if(key.equals(PROVISIONING_ENFORCE_PREFIX_PROP))
{
checkEnforcePrefix((String)value);
continue;
}
/* check that properties is allowed */
if(!isPrefixAllowed(key))
{
continue;
}
processProperty(key, value);
}
try
{
/* save and reload the "new" configuration */
ProvisioningActivator.getConfigurationService().
storeConfiguration();
ProvisioningActivator.getConfigurationService().
reloadConfiguration();
}
catch(Exception e)
{
logger.error("Cannot reload configuration");
}
}
catch(IOException e)
{
logger.warn("Error during load of provisioning file");
}
finally
{
try
{
in.close();
}
catch(IOException e)
{
}
}
}
/**
* Check if a property name belongs to the allowed prefixes.
*
* @param key property key name
* @return true if key is allowed, false otherwise
*/
private boolean isPrefixAllowed(String key)
{
if(allowedPrefixes.size() > 0)
{
for(String s : allowedPrefixes)
{
if(key.startsWith(s))
{
return true;
}
}
/* current property prefix is not allowed */
return false;
}
else
{
/* no allowed prefixes configured so key is valid by default */
return true;
}
}
/**
* Process a new property. If value equals "${null}", it means to remove the
* property in the configuration service. If the key name end with
* "PASSWORD", its value is encrypted through credentials storage service,
* otherwise the property is added/updated in the configuration service.
*
* @param key property key name
* @param value property value
*/
private void processProperty(String key, Object value)
{
if((value instanceof String) && value.equals("${null}"))
{
ProvisioningActivator.getConfigurationService().removeProperty(key);
}
else if(key.endsWith(".PASSWORD"))
{
/* password => credentials storage service */
ProvisioningActivator.getCredentialsStorageService().storePassword(
key.substring(0, key.lastIndexOf(".")),
(String)value);
if(logger.isInfoEnabled())
logger.info(key +"=<password hidden>");
return;
}
else if(key.startsWith(SYSTEM_PROP_PREFIX))
{
String sysKey = key.substring(
SYSTEM_PROP_PREFIX.length(), key.length());
System.setProperty(sysKey, (String)value);
}
else
{
ProvisioningActivator.getConfigurationService().setProperty(key,
value);
}
if(logger.isInfoEnabled())
logger.info(key + "=" + value);
}
/**
* Walk through all properties and make sure all properties keys match
* a specific set of prefixes defined in configuration.
*
* @param enforcePrefix list of enforce prefix.
*/
private void checkEnforcePrefix(String enforcePrefix)
{
ConfigurationService config =
ProvisioningActivator.getConfigurationService();
String prefixes[] = null;
if(enforcePrefix == null)
{
return;
}
/* must escape the | character */
prefixes = enforcePrefix.split("\\|");
/* get all properties */
for (String key : config.getAllPropertyNames())
{
boolean isValid = false;
for(String k : prefixes)
{
if(key.startsWith(k))
{
isValid = true;
break;
}
}
/* property name does is not in the enforce prefix list
* so remove it
*/
if(!isValid)
{
config.removeProperty(key);
}
}
}
}