/** * Copyright (c) 2014, the Railo Company Ltd. * Copyright (c) 2015, Lucee Assosication Switzerland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * */ package lucee.runtime.config; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import lucee.print; import lucee.commons.collection.LinkedHashMapMaxSize; import lucee.commons.collection.MapFactory; import lucee.commons.digest.Hash; import lucee.commons.digest.HashUtil; import lucee.commons.io.SystemUtil; import lucee.commons.io.res.Resource; import lucee.commons.io.res.ResourcesImpl; import lucee.commons.io.res.filter.ExtensionResourceFilter; import lucee.commons.lang.ClassUtil; import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.StringUtil; import lucee.commons.lang.SystemOut; import lucee.loader.engine.CFMLEngine; import lucee.loader.engine.CFMLEngineFactory; import lucee.loader.util.ExtensionFilter; import lucee.runtime.CFMLFactory; import lucee.runtime.CFMLFactoryImpl; import lucee.runtime.Mapping; import lucee.runtime.MappingImpl; import lucee.runtime.db.ClassDefinition; import lucee.runtime.engine.CFMLEngineImpl; import lucee.runtime.engine.ThreadQueue; import lucee.runtime.exp.ApplicationException; import lucee.runtime.exp.ExpressionException; import lucee.runtime.exp.PageException; import lucee.runtime.extension.ExtensionDefintion; import lucee.runtime.extension.RHExtension; import lucee.runtime.monitor.ActionMonitor; import lucee.runtime.monitor.ActionMonitorCollector; import lucee.runtime.monitor.IntervallMonitor; import lucee.runtime.monitor.RequestMonitor; import lucee.runtime.net.amf.AMFEngine; import lucee.runtime.net.http.ReqRspUtil; import lucee.runtime.op.Caster; import lucee.runtime.op.Decision; import lucee.runtime.osgi.OSGiUtil.BundleDefinition; import lucee.runtime.reflection.Reflector; import lucee.runtime.security.SecurityManager; import lucee.runtime.security.SecurityManagerImpl; import lucee.runtime.type.scope.Cluster; import lucee.runtime.type.scope.ClusterRemote; import lucee.runtime.type.scope.ClusterWrap; import lucee.runtime.type.util.ArrayUtil; import lucee.transformer.library.function.FunctionLib; import lucee.transformer.library.function.FunctionLibException; import lucee.transformer.library.function.FunctionLibFactory; import lucee.transformer.library.tag.TagLib; import lucee.transformer.library.tag.TagLibException; import lucee.transformer.library.tag.TagLibFactory; /** * config server impl */ public final class ConfigServerImpl extends ConfigImpl implements ConfigServer { private static final long FIVE_SECONDS = 5000; private final CFMLEngineImpl engine; private Map<String,CFMLFactory> initContextes; //private Map contextes; private SecurityManager defaultSecurityManager; private Map<String,SecurityManager> managers=MapFactory.<String,SecurityManager>getConcurrentMap(); Password defaultPassword; private Resource rootDir; private URL updateLocation; private String updateType=""; private ConfigListener configListener; private Map<String, String> labels; private RequestMonitor[] requestMonitors; private IntervallMonitor[] intervallMonitors; private ActionMonitorCollector actionMonitorCollector; private boolean monitoringEnabled=false; private int delay=1; private boolean captcha=false; private boolean rememberMe=true; //private static ConfigServerImpl instance; private String[] authKeys; private String idPro; private LinkedHashMapMaxSize<Long,String> previousNonces=new LinkedHashMapMaxSize<Long,String>(100); private int permGenCleanUpThreshold=60; final TagLib cfmlCoreTLDs; final TagLib luceeCoreTLDs; final FunctionLib cfmlCoreFLDs; final FunctionLib luceeCoreFLDs; private ServletConfig srvConfig; /** * @param engine * @param srvConfig * @param initContextes * @param contextes * @param configDir * @param configFile * @throws TagLibException * @throws FunctionLibException */ protected ConfigServerImpl(CFMLEngineImpl engine, Map<String,CFMLFactory> initContextes, Map<String,CFMLFactory> contextes, Resource configDir, Resource configFile) throws TagLibException, FunctionLibException { super(configDir, configFile); this.cfmlCoreTLDs=TagLibFactory.loadFromSystem(CFMLEngine.DIALECT_CFML,id); this.luceeCoreTLDs=TagLibFactory.loadFromSystem(CFMLEngine.DIALECT_LUCEE,id); this.cfmlCoreFLDs=FunctionLibFactory.loadFromSystem(CFMLEngine.DIALECT_CFML,id); this.luceeCoreFLDs=FunctionLibFactory.loadFromSystem(CFMLEngine.DIALECT_LUCEE,id); this.engine=engine; engine.setConfigServerImpl(this); this.initContextes=initContextes; //this.contextes=contextes; this.rootDir=configDir; //instance=this; } /** * @return the configListener */ @Override public ConfigListener getConfigListener() { return configListener; } /** * @param configListener the configListener to set */ @Override public void setConfigListener(ConfigListener configListener) { this.configListener = configListener; } @Override public ConfigServer getConfigServer(String password) { return this; } @Override public ConfigServer getConfigServer(String key, long timeNonce) { return this; } @Override public ConfigWeb[] getConfigWebs() { Iterator<String> it = initContextes.keySet().iterator(); ConfigWeb[] webs=new ConfigWeb[initContextes.size()]; int index=0; while(it.hasNext()) { webs[index++]=((CFMLFactoryImpl)initContextes.get(it.next())).getConfig(); } return webs; } @Override public ConfigWeb getConfigWeb(String realpath) { return getConfigWebImpl(realpath); } /** * returns CongigWeb Implementtion * @param realpath * @return ConfigWebImpl */ protected ConfigWebImpl getConfigWebImpl(String realpath) { Iterator<String> it = initContextes.keySet().iterator(); while(it.hasNext()) { ConfigWebImpl cw=((CFMLFactoryImpl)initContextes.get(it.next())).getConfigWebImpl(); if(ReqRspUtil.getRootPath(cw.getServletContext()).equals(realpath)) return cw; } return null; } public ServletContext getServletContext() { Iterator<String> it = initContextes.keySet().iterator(); while(it.hasNext()) { ConfigWebImpl cw=((CFMLFactoryImpl)initContextes.get(it.next())).getConfigWebImpl(); return cw.getServletContext(); } return null; } public ConfigWebImpl getConfigWebById(String id) { Iterator<String> it = initContextes.keySet().iterator(); while(it.hasNext()) { ConfigWebImpl cw=((CFMLFactoryImpl)initContextes.get(it.next())).getConfigWebImpl(); if(cw.getIdentification().getId().equals(id)) return cw; } return null; } /** * @return JspFactoryImpl array */ public CFMLFactoryImpl[] getJSPFactories() { Iterator<String> it = initContextes.keySet().iterator(); CFMLFactoryImpl[] factories=new CFMLFactoryImpl[initContextes.size()]; int index=0; while(it.hasNext()) { factories[index++]=(CFMLFactoryImpl)initContextes.get(it.next()); } return factories; } @Override public Map<String,CFMLFactory> getJSPFactoriesAsMap() { return initContextes; } @Override public SecurityManager getSecurityManager(String id) { Object o=managers.get(id); if(o!=null) return (SecurityManager) o; if(defaultSecurityManager==null) { defaultSecurityManager = SecurityManagerImpl.getOpenSecurityManager(); } return defaultSecurityManager.cloneSecurityManager(); } @Override public boolean hasIndividualSecurityManager(String id) { return managers.containsKey(id); } /** * @param defaultSecurityManager */ protected void setDefaultSecurityManager(SecurityManager defaultSecurityManager) { this.defaultSecurityManager=defaultSecurityManager; } /** * @param id * @param securityManager */ protected void setSecurityManager(String id, SecurityManager securityManager) { managers.put(id,securityManager); } /** * @param id */ protected void removeSecurityManager(String id) { managers.remove(id); } @Override public SecurityManager getDefaultSecurityManager() { return defaultSecurityManager; } /** * @return Returns the defaultPassword. */ protected Password getDefaultPassword() { return defaultPassword; } /** * @param defaultPassword The defaultPassword to set. */ protected void setDefaultPassword(Password defaultPassword) { this.defaultPassword = defaultPassword; } @Override public CFMLEngine getCFMLEngine() { return getEngine(); } @Override public CFMLEngine getEngine() { return engine; } /** * @return Returns the rootDir. */ @Override public Resource getRootDirectory() { return rootDir; } @Override public String getUpdateType() { return updateType; } @Override public void setUpdateType(String updateType) { if(!StringUtil.isEmpty(updateType)) this.updateType = updateType; } @Override public URL getUpdateLocation() { return updateLocation; } @Override public void setUpdateLocation(URL updateLocation) { this.updateLocation = updateLocation; } @Override public void setUpdateLocation(String strUpdateLocation) throws MalformedURLException { setUpdateLocation(new URL(strUpdateLocation)); } @Override public void setUpdateLocation(String strUpdateLocation, URL defaultValue) { try { setUpdateLocation(strUpdateLocation); } catch (MalformedURLException e) { setUpdateLocation(defaultValue); } } @Override public SecurityManager getSecurityManager() { SecurityManagerImpl sm = (SecurityManagerImpl) getDefaultSecurityManager();//.cloneSecurityManager(); //sm.setAccess(SecurityManager.TYPE_ACCESS_READ,SecurityManager.ACCESS_PROTECTED); //sm.setAccess(SecurityManager.TYPE_ACCESS_WRITE,SecurityManager.ACCESS_PROTECTED); return sm; } public void setLabels(Map<String, String> labels) { this.labels=labels; } public Map<String, String> getLabels() { if(labels==null) labels=new HashMap<String, String>(); return labels; } private ThreadQueue threadQueue; public ThreadQueue setThreadQueue(ThreadQueue threadQueue) { return this.threadQueue=threadQueue; } @Override public ThreadQueue getThreadQueue() { return threadQueue; } @Override public RequestMonitor[] getRequestMonitors() { return requestMonitors; } @Override public RequestMonitor getRequestMonitor(String name) throws ApplicationException { if(requestMonitors!=null)for(int i=0;i<requestMonitors.length;i++){ if(requestMonitors[i].getName().equalsIgnoreCase(name)) return requestMonitors[i]; } throw new ApplicationException("there is no request monitor registered with name ["+name+"]"); } protected void setRequestMonitors(RequestMonitor[] monitors) { this.requestMonitors=monitors;; } @Override public IntervallMonitor[] getIntervallMonitors() { return intervallMonitors; } @Override public IntervallMonitor getIntervallMonitor(String name) throws ApplicationException { if(intervallMonitors!=null)for(int i=0;i<intervallMonitors.length;i++){ if(intervallMonitors[i].getName().equalsIgnoreCase(name)) return intervallMonitors[i]; } throw new ApplicationException("there is no intervall monitor registered with name ["+name+"]"); } protected void setIntervallMonitors(IntervallMonitor[] monitors) { this.intervallMonitors=monitors;; } public void setActionMonitorCollector(ActionMonitorCollector actionMonitorCollector) { this.actionMonitorCollector=actionMonitorCollector; } public ActionMonitorCollector getActionMonitorCollector() { return actionMonitorCollector; } @Override public ActionMonitor getActionMonitor(String name) { return actionMonitorCollector==null?null:actionMonitorCollector.getActionMonitor(name); } @Override public boolean isMonitoringEnabled() { return monitoringEnabled; } protected void setMonitoringEnabled(boolean monitoringEnabled) { this.monitoringEnabled=monitoringEnabled;; } protected void setLoginDelay(int delay) { this.delay=delay; } protected void setLoginCaptcha(boolean captcha) { this.captcha=captcha; } protected void setRememberMe(boolean rememberMe) { this.rememberMe=rememberMe; } @Override public int getLoginDelay() { return delay; } @Override public boolean getLoginCaptcha() { return captcha; } @Override public boolean getRememberMe() { return rememberMe; } @Override public void reset() { super.reset(); getThreadQueue().clear(); } @Override public Resource getSecurityDirectory(){ Resource cacerts=null; // javax.net.ssl.trustStore String trustStore = SystemUtil.getPropertyEL("javax.net.ssl.trustStore"); if(trustStore!=null){ cacerts = ResourcesImpl.getFileResourceProvider().getResource(trustStore); } // security/cacerts if(cacerts==null || !cacerts.exists()) { cacerts = getConfigDir().getRealResource("security/cacerts"); if(!cacerts.exists())cacerts.mkdirs(); } return cacerts; } @Override public void checkPermGenSpace(boolean check) { int promille=SystemUtil.getFreePermGenSpacePromille(); long kbFreePermSpace=SystemUtil.getFreePermGenSpaceSize()/1024; int percentageAvailable = SystemUtil.getPermGenFreeSpaceAsAPercentageOfAvailable(); // Pen Gen Space info not available indicated by a return of -1 if(check && kbFreePermSpace < 0) { if(countLoadedPages() > 2000) shrink(); } else if (check && percentageAvailable < permGenCleanUpThreshold) { shrink(); if (permGenCleanUpThreshold >= 5) { //adjust the threshold allowed down so the amount of permgen can slowly grow to its allocated space up to 100% setPermGenCleanUpThreshold(permGenCleanUpThreshold - 5); } else { SystemOut.printDate(getErrWriter()," Free Perm Gen Space is less than 5% free: shrinking all template classloaders : consider increasing allocated Perm Gen Space"); } } else if(check && kbFreePermSpace < 2048) { SystemOut.printDate(getErrWriter()," Free Perm Gen Space is less than 2Mb (free:"+((SystemUtil.getFreePermGenSpaceSize()/1024))+"kb), shrinking all template classloaders"); // first request a GC and then check if it helps System.gc(); if((SystemUtil.getFreePermGenSpaceSize()/1024) < 2048) { shrink(); } } } private void shrink() { ConfigWeb[] webs = getConfigWebs(); int count=0; for(int i=0;i<webs.length;i++){ count+=shrink((ConfigWebImpl) webs[i],false); } if(count==0) { for(int i=0;i<webs.length;i++){ shrink((ConfigWebImpl) webs[i],true); } } } private static int shrink(ConfigWebImpl config, boolean force) { int count=0; count+=shrink(config.getMappings(),force); count+=shrink(config.getCustomTagMappings(),force); count+=shrink(config.getComponentMappings(),force); count+=shrink(config.getFunctionMapping(),force); count+=shrink(config.getServerFunctionMapping(),force); count+=shrink(config.getTagMapping(),force); count+=shrink(config.getServerTagMapping(),force); //count+=shrink(config.getServerTagMapping(),force); return count; } private static int shrink(Mapping[] mappings, boolean force) { int count=0; for(int i=0;i<mappings.length;i++){ count+=shrink(mappings[i],force); } return count; } private static int shrink(Mapping mapping, boolean force) { try { //PCLCollection pcl = ((MappingImpl)mapping).getPCLCollection(); //if(pcl!=null)return pcl.shrink(force); ((MappingImpl)mapping).shrink(); } catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} return 0; } public int getPermGenCleanUpThreshold() { return permGenCleanUpThreshold; } public void setPermGenCleanUpThreshold(int permGenCleanUpThreshold) { this.permGenCleanUpThreshold = permGenCleanUpThreshold; } public long countLoadedPages() { /*long count=0; ConfigWeb[] webs = getConfigWebs(); for(int i=0;i<webs.length;i++){ count+=_count((ConfigWebImpl) webs[i]); } return count; */ return -1; // MUST implement } /*private static long _countx(ConfigWebImpl config) { long count=0; count+=_count(config.getMappings()); count+=_count(config.getCustomTagMappings()); count+=_count(config.getComponentMappings()); count+=_count(config.getFunctionMapping()); count+=_count(config.getServerFunctionMapping()); count+=_count(config.getTagMapping()); count+=_count(config.getServerTagMapping()); //count+=_count(((ConfigWebImpl)config).getServerTagMapping()); return count; }*/ /*private static long _count(Mapping[] mappings) { long count=0; for(int i=0;i<mappings.length;i++){ count+=_count(mappings[i]); } return count; }*/ /*private static long _countx(Mapping mapping) { PCLCollection pcl = ((MappingImpl)mapping).getPCLCollection(); return pcl==null?0:pcl.count(); }*/ @Override public Cluster createClusterScope() throws PageException { Cluster cluster=null; try { if(Reflector.isInstaneOf(getClusterClass(), Cluster.class)){ cluster=(Cluster) ClassUtil.loadInstance( getClusterClass(), ArrayUtil.OBJECT_EMPTY ); cluster.init(this); } else if(Reflector.isInstaneOf(getClusterClass(), ClusterRemote.class)){ ClusterRemote cb=(ClusterRemote) ClassUtil.loadInstance( getClusterClass(), ArrayUtil.OBJECT_EMPTY ); cluster=new ClusterWrap(this,cb); //cluster.init(cs); } } catch (Exception e) { throw Caster.toPageException(e); } return cluster; } @Override public boolean hasServerPassword() { return hasPassword(); } public String[] getInstalledPatches() throws PageException { CFMLEngineFactory factory = getCFMLEngine().getCFMLEngineFactory(); try{ return factory.getInstalledPatches(); } catch(Throwable t){ ExceptionUtil.rethrowIfNecessary(t); try { return getInstalledPatchesOld(factory); } catch (Exception e1) { throw Caster.toPageException(e1); } } } private String[] getInstalledPatchesOld(CFMLEngineFactory factory) throws IOException { File patchDir = new File(factory.getResourceRoot(),"patches"); if(!patchDir.exists())patchDir.mkdirs(); File[] patches=patchDir.listFiles(new ExtensionFilter(new String[]{"."+getCoreExtension()})); List<String> list=new ArrayList<String>(); String name; int extLen=getCoreExtension().length()+1; for(int i=0;i<patches.length;i++) { name=patches[i].getName(); name=name.substring(0, name.length()-extLen); list.add(name); } String[] arr = list.toArray(new String[list.size()]); Arrays.sort(arr); return arr; } private String getCoreExtension() { return "lco"; } @Override public boolean allowRequestTimeout() { return engine.allowRequestTimeout(); } private boolean fullNullSupport=false; private IdentificationServer id; private String libHash; private ClassDefinition<AMFEngine> amfEngineCD; private Map<String, String> amfEngineArgs; private List<ExtensionDefintion> localExtensions; private long localExtHash; protected void setFullNullSupport(boolean fullNullSupport) { this.fullNullSupport=fullNullSupport; } @Override public boolean getFullNullSupport() { return fullNullSupport; } public String[] getAuthenticationKeys() { return authKeys==null?new String[0]:authKeys; } protected void setAuthenticationKeys(String[] authKeys) { this.authKeys = authKeys; } public ConfigServer getConfigServer(String key,String nonce) { return this; } public void checkAccess(Password password) throws ExpressionException { if(!hasPassword()) throw new ExpressionException("Cannot access, no password is defined"); if(!passwordEqual(password)) throw new ExpressionException("No access, password is invalid"); } public void checkAccess(String key, long timeNonce) throws PageException { if(previousNonces.containsKey(timeNonce)) { long now = System.currentTimeMillis(); long diff=timeNonce>now?timeNonce-now:now-timeNonce; if(diff>10) throw new ApplicationException("nonce was already used, same nonce can only be used once"); } long now = System.currentTimeMillis()+getTimeServerOffset(); if(timeNonce>(now+FIVE_SECONDS) || timeNonce<(now-FIVE_SECONDS)) throw new ApplicationException("nonce is outdated (timserver offset:"+getTimeServerOffset()+")"); previousNonces.put(timeNonce,""); String[] keys=getAuthenticationKeys(); // check if one of the keys matching String hash; for(int i=0;i<keys.length;i++){ try { hash=Hash.hash(keys[i], Caster.toString(timeNonce), Hash.ALGORITHM_SHA_256, Hash.ENCODING_HEX); if(hash.equals(key)) return; } catch (NoSuchAlgorithmException e) { throw Caster.toPageException(e); } } throw new ApplicationException("No access, no matching authentication key found"); } @Override public IdentificationServer getIdentification() { return id; } protected void setIdentification(IdentificationServer id) { this.id=id; } @Override public Collection<BundleDefinition> getAllExtensionBundleDefintions() { Map<String,BundleDefinition> rtn=new HashMap<>(); // server (this) Iterator<BundleDefinition> itt = getExtensionBundleDefintions().iterator(); BundleDefinition bd; while(itt.hasNext()){ bd = itt.next(); rtn.put(bd.getName()+"|"+bd.getVersionAsString(), bd); } // webs ConfigWeb[] cws = getConfigWebs(); for(ConfigWeb cw:cws) { itt = ((ConfigImpl)cw).getExtensionBundleDefintions().iterator(); while(itt.hasNext()){ bd = itt.next(); rtn.put(bd.getName()+"|"+bd.getVersionAsString(), bd); } } return rtn.values(); } @Override public Collection<RHExtension> getAllRHExtensions() { Map<String,RHExtension> rtn=new HashMap<>(); // server (this) RHExtension[] arr = getRHExtensions(); for(RHExtension rhe:arr){ rtn.put(rhe.getId(),rhe); } // webs ConfigWeb[] cws = getConfigWebs(); for(ConfigWeb cw:cws) { arr = ((ConfigWebImpl)cw).getRHExtensions(); for(RHExtension rhe:arr){ rtn.put(rhe.getId(),rhe); } } return rtn.values(); } protected void setLibHash(String libHash) { this.libHash=libHash; } protected String getLibHash() { return libHash; } @Override public Resource getLocalExtensionProviderDirectory() { Resource dir = getConfigDir().getRealResource("extensions/available"); if(!dir.exists())dir.mkdirs(); return dir; } protected void setAMFEngine(ClassDefinition<AMFEngine> cd, Map<String, String> args) { amfEngineCD=cd; amfEngineArgs=args; } public ClassDefinition<AMFEngine> getAMFEngineClassDefinition() { return amfEngineCD; } public Map<String, String> getAMFEngineArgs() { return amfEngineArgs; } @Override public RHExtension[] getServerRHExtensions() { return getRHExtensions(); } @Override public List<ExtensionDefintion> loadLocalExtensions() { Resource[] locReses = getLocalExtensionProviderDirectory().listResources(new ExtensionResourceFilter(".lex")); if(localExtensions==null || localExtensions.size()!=locReses.length || extHash(locReses)==localExtHash) { localExtensions=new ArrayList<ExtensionDefintion>(); Map<String,String> map=new HashMap<String,String>(); RHExtension ext; String v,fileName,uuid,version; ExtensionDefintion ed; for(int i=0;i<locReses.length;i++) { ed=null; // we stay happy with the file name when it has the right pattern (uuid-version.lex) fileName=locReses[i].getName(); if(fileName.length()>39) { uuid=fileName.substring(0, 35); version=fileName.substring(36, fileName.length()-4); if(Decision.isUUId(uuid)) { ed = new ExtensionDefintion(uuid, version); ed.setSource(this,locReses[i]); } } if(ed==null) { try { ext=new RHExtension(this,locReses[i],false); ed = new ExtensionDefintion(ext.getId(), ext.getVersion()); ed.setSource(ext); } catch(Exception e) {e.printStackTrace();} } if(ed!=null) { // check if we already have an extension with the same id to avoid having more than once v=map.get(ed.getId()); if(v!=null && v.compareToIgnoreCase(ed.getId())>0) continue; map.put(ed.getId(), ed.getVersion()); localExtensions.add(ed); } } localExtHash=extHash(locReses); } return localExtensions; } private long extHash(Resource[] locReses) { StringBuilder sb=new StringBuilder(); for(Resource locRes:locReses){ sb.append(locRes.getAbsolutePath()).append(';'); } return HashUtil.create64BitHash(sb); } }