001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.runtime.config; 020 021import java.io.File; 022import java.io.IOException; 023import java.net.MalformedURLException; 024import java.net.URL; 025import java.security.NoSuchAlgorithmException; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.HashMap; 029import java.util.Iterator; 030import java.util.List; 031import java.util.Map; 032 033import lucee.commons.collection.LinkedHashMapMaxSize; 034import lucee.commons.collection.MapFactory; 035import lucee.commons.digest.Hash; 036import lucee.commons.io.SystemUtil; 037import lucee.commons.io.res.Resource; 038import lucee.commons.io.res.ResourcesImpl; 039import lucee.commons.lang.ClassUtil; 040import lucee.commons.lang.ExceptionUtil; 041import lucee.commons.lang.PCLCollection; 042import lucee.commons.lang.StringUtil; 043import lucee.commons.lang.SystemOut; 044import lucee.loader.engine.CFMLEngine; 045import lucee.loader.engine.CFMLEngineFactory; 046import lucee.loader.util.ExtensionFilter; 047import lucee.runtime.CFMLFactory; 048import lucee.runtime.CFMLFactoryImpl; 049import lucee.runtime.Mapping; 050import lucee.runtime.MappingImpl; 051import lucee.runtime.engine.CFMLEngineImpl; 052import lucee.runtime.engine.ThreadQueue; 053import lucee.runtime.exp.ApplicationException; 054import lucee.runtime.exp.ExpressionException; 055import lucee.runtime.exp.PageException; 056import lucee.runtime.monitor.ActionMonitorCollector; 057import lucee.runtime.monitor.IntervallMonitor; 058import lucee.runtime.monitor.RequestMonitor; 059import lucee.runtime.net.http.ReqRspUtil; 060import lucee.runtime.op.Caster; 061import lucee.runtime.reflection.Reflector; 062import lucee.runtime.security.SecurityManager; 063import lucee.runtime.security.SecurityManagerImpl; 064import lucee.runtime.type.scope.Cluster; 065import lucee.runtime.type.scope.ClusterRemote; 066import lucee.runtime.type.scope.ClusterWrap; 067import lucee.runtime.type.util.ArrayUtil; 068 069/** 070 * config server impl 071 */ 072public final class ConfigServerImpl extends ConfigImpl implements ConfigServer { 073 074 private static final long FIVE_SECONDS = 5000; 075 076 private final CFMLEngineImpl engine; 077 private Map<String,CFMLFactory> initContextes; 078 //private Map contextes; 079 private SecurityManager defaultSecurityManager; 080 private Map<String,SecurityManager> managers=MapFactory.<String,SecurityManager>getConcurrentMap(); 081 Password defaultPassword; 082 private Resource rootDir; 083 private URL updateLocation; 084 private String updateType=""; 085 private ConfigListener configListener; 086 private Map<String, String> labels; 087 private RequestMonitor[] requestMonitors; 088 private IntervallMonitor[] intervallMonitors; 089 private ActionMonitorCollector actionMonitorCollector; 090 091 private boolean monitoringEnabled=false; 092 private int delay=1; 093 private boolean captcha=false; 094 private boolean rememberMe=true; 095 //private static ConfigServerImpl instance; 096 097 private String[] authKeys; 098 private String idPro; 099 100 private LinkedHashMapMaxSize<Long,String> previousNonces=new LinkedHashMapMaxSize<Long,String>(100); 101 102 private int permGenCleanUpThreshold=60; 103 104 /** 105 * @param engine 106 * @param initContextes 107 * @param contextes 108 * @param configDir 109 * @param configFile 110 */ 111 protected ConfigServerImpl(CFMLEngineImpl engine,Map<String,CFMLFactory> initContextes, Map<String,CFMLFactory> contextes, Resource configDir, Resource configFile) { 112 super(null,configDir, configFile); 113 this.engine=engine; 114 engine.setConfigServerImpl(this); 115 this.initContextes=initContextes; 116 //this.contextes=contextes; 117 this.rootDir=configDir; 118 //instance=this; 119 } 120 121 /** 122 * @return the configListener 123 */ 124 public ConfigListener getConfigListener() { 125 return configListener; 126 } 127 128 /** 129 * @param configListener the configListener to set 130 */ 131 public void setConfigListener(ConfigListener configListener) { 132 this.configListener = configListener; 133 } 134 135 @Override 136 public ConfigServer getConfigServer(String password) { 137 return this; 138 } 139 140 @Override 141 public ConfigWeb[] getConfigWebs() { 142 143 Iterator<String> it = initContextes.keySet().iterator(); 144 ConfigWeb[] webs=new ConfigWeb[initContextes.size()]; 145 int index=0; 146 while(it.hasNext()) { 147 webs[index++]=((CFMLFactoryImpl)initContextes.get(it.next())).getConfig(); 148 } 149 return webs; 150 } 151 152 @Override 153 public ConfigWeb getConfigWeb(String relpath) { 154 return getConfigWebImpl(relpath); 155 } 156 157 /** 158 * returns CongigWeb Implementtion 159 * @param relpath 160 * @return ConfigWebImpl 161 */ 162 protected ConfigWebImpl getConfigWebImpl(String relpath) { 163 Iterator<String> it = initContextes.keySet().iterator(); 164 while(it.hasNext()) { 165 ConfigWebImpl cw=((CFMLFactoryImpl)initContextes.get(it.next())).getConfigWebImpl(); 166 if(ReqRspUtil.getRootPath(cw.getServletContext()).equals(relpath)) 167 return cw; 168 } 169 return null; 170 } 171 172 public ConfigWebImpl getConfigWebById(String id) { 173 Iterator<String> it = initContextes.keySet().iterator(); 174 175 while(it.hasNext()) { 176 ConfigWebImpl cw=((CFMLFactoryImpl)initContextes.get(it.next())).getConfigWebImpl(); 177 if(cw.getId().equals(id)) 178 return cw; 179 } 180 return null; 181 } 182 183 public String getIdPro() { 184 if(idPro==null){ 185 idPro = getId(getSecurityKey(),getSecurityToken(),true,null); 186 } 187 return idPro; 188 } 189 190 /** 191 * @return JspFactoryImpl array 192 */ 193 public CFMLFactoryImpl[] getJSPFactories() { 194 Iterator<String> it = initContextes.keySet().iterator(); 195 CFMLFactoryImpl[] factories=new CFMLFactoryImpl[initContextes.size()]; 196 int index=0; 197 while(it.hasNext()) { 198 factories[index++]=(CFMLFactoryImpl)initContextes.get(it.next()); 199 } 200 return factories; 201 } 202 @Override 203 public Map<String,CFMLFactory> getJSPFactoriesAsMap() { 204 return initContextes; 205 } 206 207 @Override 208 public SecurityManager getSecurityManager(String id) { 209 Object o=managers.get(id); 210 if(o!=null) return (SecurityManager) o; 211 if(defaultSecurityManager==null) { 212 defaultSecurityManager = SecurityManagerImpl.getOpenSecurityManager(); 213 } 214 return defaultSecurityManager.cloneSecurityManager(); 215 } 216 217 @Override 218 public boolean hasIndividualSecurityManager(String id) { 219 return managers.containsKey(id); 220 } 221 222 /** 223 * @param defaultSecurityManager 224 */ 225 protected void setDefaultSecurityManager(SecurityManager defaultSecurityManager) { 226 this.defaultSecurityManager=defaultSecurityManager; 227 } 228 229 /** 230 * @param id 231 * @param securityManager 232 */ 233 protected void setSecurityManager(String id, SecurityManager securityManager) { 234 managers.put(id,securityManager); 235 } 236 237 /** 238 * @param id 239 */ 240 protected void removeSecurityManager(String id) { 241 managers.remove(id); 242 } 243 244 @Override 245 public SecurityManager getDefaultSecurityManager() { 246 return defaultSecurityManager; 247 } 248 /** 249 * @return Returns the defaultPassword. 250 */ 251 protected Password getDefaultPassword() { 252 return defaultPassword; 253 } 254 255 256 /** 257 * @param defaultPassword The defaultPassword to set. 258 */ 259 protected void setDefaultPassword(Password defaultPassword) { 260 this.defaultPassword = defaultPassword; 261 } 262 263 @Override 264 public CFMLEngine getCFMLEngine() { 265 return engine; 266 } 267 268 269 /** 270 * @return Returns the rootDir. 271 */ 272 public Resource getRootDirectory() { 273 return rootDir; 274 } 275 276 @Override 277 public String getUpdateType() { 278 return updateType; 279 } 280 281 @Override 282 public void setUpdateType(String updateType) { 283 if(!StringUtil.isEmpty(updateType)) 284 this.updateType = updateType; 285 } 286 287 @Override 288 public URL getUpdateLocation() { 289 return updateLocation; 290 } 291 292 @Override 293 public void setUpdateLocation(URL updateLocation) { 294 this.updateLocation = updateLocation; 295 } 296 297 @Override 298 public void setUpdateLocation(String strUpdateLocation) throws MalformedURLException { 299 setUpdateLocation(new URL(strUpdateLocation)); 300 } 301 302 @Override 303 public void setUpdateLocation(String strUpdateLocation, URL defaultValue) { 304 try { 305 setUpdateLocation(strUpdateLocation); 306 } catch (MalformedURLException e) { 307 setUpdateLocation(defaultValue); 308 } 309 } 310 311 @Override 312 public SecurityManager getSecurityManager() { 313 SecurityManagerImpl sm = (SecurityManagerImpl) getDefaultSecurityManager();//.cloneSecurityManager(); 314 //sm.setAccess(SecurityManager.TYPE_ACCESS_READ,SecurityManager.ACCESS_PROTECTED); 315 //sm.setAccess(SecurityManager.TYPE_ACCESS_WRITE,SecurityManager.ACCESS_PROTECTED); 316 return sm; 317 } 318 319 public void setLabels(Map<String, String> labels) { 320 this.labels=labels; 321 } 322 public Map<String, String> getLabels() { 323 if(labels==null) labels=new HashMap<String, String>(); 324 return labels; 325 } 326 327 328 private ThreadQueue threadQueue; 329 public ThreadQueue setThreadQueue(ThreadQueue threadQueue) { 330 return this.threadQueue=threadQueue; 331 } 332 333 public ThreadQueue getThreadQueue() { 334 return threadQueue; 335 } 336 337 public RequestMonitor[] getRequestMonitors() { 338 return requestMonitors; 339 } 340 341 public RequestMonitor getRequestMonitor(String name) throws ApplicationException { 342 if(requestMonitors!=null)for(int i=0;i<requestMonitors.length;i++){ 343 if(requestMonitors[i].getName().equalsIgnoreCase(name)) 344 return requestMonitors[i]; 345 } 346 throw new ApplicationException("there is no request monitor registered with name ["+name+"]"); 347 } 348 349 protected void setRequestMonitors(RequestMonitor[] monitors) { 350 this.requestMonitors=monitors;; 351 } 352 public IntervallMonitor[] getIntervallMonitors() { 353 return intervallMonitors; 354 } 355 356 public IntervallMonitor getIntervallMonitor(String name) throws ApplicationException { 357 if(intervallMonitors!=null)for(int i=0;i<intervallMonitors.length;i++){ 358 if(intervallMonitors[i].getName().equalsIgnoreCase(name)) 359 return intervallMonitors[i]; 360 } 361 throw new ApplicationException("there is no intervall monitor registered with name ["+name+"]"); 362 } 363 364 protected void setIntervallMonitors(IntervallMonitor[] monitors) { 365 this.intervallMonitors=monitors;; 366 } 367 368 369 public void setActionMonitorCollector(ActionMonitorCollector actionMonitorCollector) { 370 this.actionMonitorCollector=actionMonitorCollector; 371 } 372 373 public ActionMonitorCollector getActionMonitorCollector() { 374 return actionMonitorCollector; 375 } 376 377 public Object getActionMonitor(String name) { // FUTURE return ActionMonitor 378 return actionMonitorCollector==null?null:actionMonitorCollector.getActionMonitor(name); 379 } 380 381 @Override 382 public boolean isMonitoringEnabled() { 383 return monitoringEnabled; 384 } 385 386 protected void setMonitoringEnabled(boolean monitoringEnabled) { 387 this.monitoringEnabled=monitoringEnabled;; 388 } 389 390 391 protected void setLoginDelay(int delay) { 392 this.delay=delay; 393 } 394 395 protected void setLoginCaptcha(boolean captcha) { 396 this.captcha=captcha; 397 } 398 protected void setRememberMe(boolean rememberMe) { 399 this.rememberMe=rememberMe; 400 } 401 402 403 404 @Override 405 public int getLoginDelay() { 406 return delay; 407 } 408 409 @Override 410 public boolean getLoginCaptcha() { 411 return captcha; 412 } 413 414 @Override 415 public boolean getRememberMe() { 416 return rememberMe; 417 } 418 419 public void reset() { 420 super.reset(); 421 getThreadQueue().clear(); 422 } 423 424 @Override 425 public Resource getSecurityDirectory(){ 426 Resource cacerts=null; 427 // javax.net.ssl.trustStore 428 String trustStore = SystemUtil.getPropertyEL("javax.net.ssl.trustStore"); 429 if(trustStore!=null){ 430 cacerts = ResourcesImpl.getFileResourceProvider().getResource(trustStore); 431 } 432 433 // security/cacerts 434 if(cacerts==null || !cacerts.exists()) { 435 cacerts = getConfigDir().getRealResource("security/cacerts"); 436 if(!cacerts.exists())cacerts.mkdirs(); 437 } 438 return cacerts; 439 } 440 441 @Override 442 public void checkPermGenSpace(boolean check) { 443 int promille=SystemUtil.getFreePermGenSpacePromille(); 444 445 long kbFreePermSpace=SystemUtil.getFreePermGenSpaceSize()/1024; 446 int percentageAvailable = SystemUtil.getPermGenFreeSpaceAsAPercentageOfAvailable(); 447 448 449 // Pen Gen Space info not available indicated by a return of -1 450 if(check && kbFreePermSpace < 0) { 451 if(countLoadedPages() > 2000) 452 shrink(); 453 } 454 else if (check && percentageAvailable < permGenCleanUpThreshold) { 455 shrink(); 456 if (permGenCleanUpThreshold >= 5) { 457 //adjust the threshold allowed down so the amount of permgen can slowly grow to its allocated space up to 100% 458 setPermGenCleanUpThreshold(permGenCleanUpThreshold - 5); 459 } 460 else { 461 SystemOut.printDate(getErrWriter()," Free Perm Gen Space is less than 5% free: shrinking all template classloaders : consider increasing allocated Perm Gen Space"); 462 } 463 } 464 else if(check && kbFreePermSpace < 2048) { 465 SystemOut.printDate(getErrWriter()," Free Perm Gen Space is less than 2Mb (free:"+((SystemUtil.getFreePermGenSpaceSize()/1024))+"kb), shrinking all template classloaders"); 466 // first request a GC and then check if it helps 467 System.gc(); 468 if((SystemUtil.getFreePermGenSpaceSize()/1024) < 2048) { 469 shrink(); 470 } 471 } 472 } 473 474 private void shrink() { 475 ConfigWeb[] webs = getConfigWebs(); 476 int count=0; 477 for(int i=0;i<webs.length;i++){ 478 count+=shrink((ConfigWebImpl) webs[i],false); 479 } 480 if(count==0) { 481 for(int i=0;i<webs.length;i++){ 482 shrink((ConfigWebImpl) webs[i],true); 483 } 484 } 485 } 486 487 private static int shrink(ConfigWebImpl config, boolean force) { 488 int count=0; 489 count+=shrink(config.getMappings(),force); 490 count+=shrink(config.getCustomTagMappings(),force); 491 count+=shrink(config.getComponentMappings(),force); 492 count+=shrink(config.getFunctionMapping(),force); 493 count+=shrink(config.getServerFunctionMapping(),force); 494 count+=shrink(config.getTagMapping(),force); 495 count+=shrink(config.getServerTagMapping(),force); 496 //count+=shrink(config.getServerTagMapping(),force); 497 return count; 498 } 499 500 private static int shrink(Mapping[] mappings, boolean force) { 501 int count=0; 502 for(int i=0;i<mappings.length;i++){ 503 count+=shrink(mappings[i],force); 504 } 505 return count; 506 } 507 508 private static int shrink(Mapping mapping, boolean force) { 509 try { 510 PCLCollection pcl = ((MappingImpl)mapping).getPCLCollection(); 511 if(pcl!=null)return pcl.shrink(force); 512 } 513 catch (Throwable t) { 514 ExceptionUtil.rethrowIfNecessary(t); 515 t.printStackTrace(); 516 } 517 return 0; 518 } 519 520 public int getPermGenCleanUpThreshold() { 521 return permGenCleanUpThreshold; 522 } 523 524 public void setPermGenCleanUpThreshold(int permGenCleanUpThreshold) { 525 this.permGenCleanUpThreshold = permGenCleanUpThreshold; 526 } 527 528 529 public long countLoadedPages() { 530 long count=0; 531 ConfigWeb[] webs = getConfigWebs(); 532 for(int i=0;i<webs.length;i++){ 533 count+=_count((ConfigWebImpl) webs[i]); 534 } 535 return count; 536 } 537 private static long _count(ConfigWebImpl config) { 538 long count=0; 539 count+=_count(config.getMappings()); 540 count+=_count(config.getCustomTagMappings()); 541 count+=_count(config.getComponentMappings()); 542 count+=_count(config.getFunctionMapping()); 543 count+=_count(config.getServerFunctionMapping()); 544 count+=_count(config.getTagMapping()); 545 count+=_count(config.getServerTagMapping()); 546 //count+=_count(((ConfigWebImpl)config).getServerTagMapping()); 547 return count; 548 } 549 550 private static long _count(Mapping[] mappings) { 551 long count=0; 552 for(int i=0;i<mappings.length;i++){ 553 count+=_count(mappings[i]); 554 } 555 return count; 556 } 557 558 private static long _count(Mapping mapping) { 559 PCLCollection pcl = ((MappingImpl)mapping).getPCLCollection(); 560 return pcl==null?0:pcl.count(); 561 } 562 563 @Override 564 public Cluster createClusterScope() throws PageException { 565 Cluster cluster=null; 566 try { 567 if(Reflector.isInstaneOf(getClusterClass(), Cluster.class)){ 568 cluster=(Cluster) ClassUtil.loadInstance( 569 getClusterClass(), 570 ArrayUtil.OBJECT_EMPTY 571 ); 572 cluster.init(this); 573 } 574 else if(Reflector.isInstaneOf(getClusterClass(), ClusterRemote.class)){ 575 ClusterRemote cb=(ClusterRemote) ClassUtil.loadInstance( 576 getClusterClass(), 577 ArrayUtil.OBJECT_EMPTY 578 ); 579 580 cluster=new ClusterWrap(this,cb); 581 //cluster.init(cs); 582 } 583 } 584 catch (Exception e) { 585 throw Caster.toPageException(e); 586 } 587 return cluster; 588 } 589 590 @Override 591 public boolean hasServerPassword() { 592 return hasPassword(); 593 } 594 595 public String[] getInstalledPatches() throws PageException { 596 CFMLEngineFactory factory = getCFMLEngine().getCFMLEngineFactory(); 597 598 try{ 599 return factory.getInstalledPatches(); 600 } 601 catch(Throwable t){ 602 ExceptionUtil.rethrowIfNecessary(t); 603 try { 604 return getInstalledPatchesOld(factory); 605 } catch (Exception e1) { 606 throw Caster.toPageException(e1); 607 } 608 } 609 } 610 611 private String[] getInstalledPatchesOld(CFMLEngineFactory factory) throws IOException { 612 File patchDir = new File(factory.getResourceRoot(),"patches"); 613 if(!patchDir.exists())patchDir.mkdirs(); 614 615 File[] patches=patchDir.listFiles(new ExtensionFilter(new String[]{"."+getCoreExtension()})); 616 617 List<String> list=new ArrayList<String>(); 618 String name; 619 int extLen=getCoreExtension().length()+1; 620 for(int i=0;i<patches.length;i++) { 621 name=patches[i].getName(); 622 name=name.substring(0, name.length()-extLen); 623 list.add(name); 624 } 625 String[] arr = list.toArray(new String[list.size()]); 626 Arrays.sort(arr); 627 return arr; 628 } 629 630 631 private String getCoreExtension() { 632 return "lco"; 633 } 634 635 @Override 636 public boolean allowRequestTimeout() { 637 return engine.allowRequestTimeout(); 638 } 639 640 641 private boolean fullNullSupport=false; 642 protected void setFullNullSupport(boolean fullNullSupport) { 643 this.fullNullSupport=fullNullSupport; 644 } 645 646 public boolean getFullNullSupport() { 647 return fullNullSupport; 648 } 649 650 public String[] getAuthenticationKeys() { 651 return authKeys==null?new String[0]:authKeys; 652 } 653 654 protected void setAuthenticationKeys(String[] authKeys) { 655 this.authKeys = authKeys; 656 } 657 658 public ConfigServer getConfigServer(String key,String nonce) { 659 return this; 660 } 661 662 public void checkAccess(String password) throws ExpressionException { 663 if(!hasPassword()) 664 throw new ExpressionException("Cannot access, no password is defined"); 665 if(!passwordEqual(password)) 666 throw new ExpressionException("No access, password is invalid"); 667 } 668 669 public void checkAccess(String key, long timeNonce) throws PageException { 670 671 if(previousNonces.containsKey(timeNonce)) { 672 long now = System.currentTimeMillis(); 673 long diff=timeNonce>now?timeNonce-now:now-timeNonce; 674 if(diff>10) 675 throw new ApplicationException("nonce was already used, same nonce can only be used once"); 676 677 678 } 679 long now = System.currentTimeMillis()+getTimeServerOffset(); 680 if(timeNonce>(now+FIVE_SECONDS) || timeNonce<(now-FIVE_SECONDS)) 681 throw new ApplicationException("nonce is outdated (timserver offset:"+getTimeServerOffset()+")"); 682 previousNonces.put(timeNonce,""); 683 684 String[] keys=getAuthenticationKeys(); 685 // check if one of the keys matching 686 String hash; 687 for(int i=0;i<keys.length;i++){ 688 try { 689 hash=Hash.hash(keys[i], Caster.toString(timeNonce), Hash.ALGORITHM_SHA_256, Hash.ENCODING_HEX); 690 if(hash.equals(key)) return; 691 } 692 catch (NoSuchAlgorithmException e) { 693 throw Caster.toPageException(e); 694 } 695 } 696 throw new ApplicationException("No access, no matching authentication key found"); 697 } 698}