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}