001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.configuration;
029
030import org.opencms.file.CmsObject;
031import org.opencms.letsencrypt.CmsLetsEncryptConfiguration;
032import org.opencms.letsencrypt.CmsLetsEncryptConfiguration.Trigger;
033import org.opencms.letsencrypt.CmsSiteConfigToLetsEncryptConfigConverter;
034import org.opencms.main.CmsLog;
035import org.opencms.main.OpenCms;
036import org.opencms.report.CmsLogReport;
037import org.opencms.site.CmsSSLMode;
038import org.opencms.site.CmsSite;
039import org.opencms.site.CmsSiteManagerImpl;
040import org.opencms.site.CmsSiteMatcher;
041
042import java.util.HashSet;
043import java.util.Iterator;
044import java.util.Locale;
045import java.util.Map;
046import java.util.concurrent.ScheduledFuture;
047import java.util.concurrent.TimeUnit;
048
049import org.apache.commons.digester3.Digester;
050
051import org.dom4j.Element;
052
053/**
054 * Class to read and write the OpenCms site configuration.<p>
055 */
056public class CmsSitesConfiguration extends A_CmsXmlConfiguration implements I_CmsXmlConfigurationWithUpdateHandler {
057
058    /** The "error" attribute. */
059    public static final String A_ERROR = "error";
060
061    /** The "errorPage" attribute. */
062    public static final String A_ERROR_PAGE = "errorPage";
063
064    /** The "exclusive" attribute. */
065    public static final String A_EXCLUSIVE = "exclusive";
066
067    /** The attribute name for the alias offset. */
068    public static final String A_OFFSET = "offset";
069
070    /** The "position" attribute. */
071    public static final String A_POSITION = "position";
072
073    /** The "server" attribute. */
074    public static final String A_SERVER = "server";
075
076    /** The "redirect" attribute. */
077    public static final String A_REDIRECT = "redirect";
078
079    /** The "title" attribute. */
080    public static final String A_TITLE = "title";
081
082    /** The ssl mode attribute.*/
083    public static final String A_SSL = "sslmode";
084
085    /** The "usePermanentRedirects" attribute. */
086    public static final String A_USE_PERMANENT_REDIRECTS = "usePermanentRedirects";
087
088    /** The "webserver" attribute. */
089    public static final String A_WEBSERVER = "webserver";
090
091    /** The name of the DTD for this configuration. */
092    public static final String CONFIGURATION_DTD_NAME = "opencms-sites.dtd";
093
094    /** The name of the default XML file for this configuration. */
095    public static final String DEFAULT_XML_FILE_NAME = "opencms-sites.xml";
096
097    /** The node name for the alias node. */
098    public static final String N_ALIAS = "alias";
099
100    /** The node name for the default-uri node. */
101    public static final String N_DEFAULT_URI = "default-uri";
102
103    /** The node name for the parameters. */
104    public static final String N_PARAMETERS = "parameters";
105
106    /** The node name for the secure site. */
107    public static final String N_SECURE = "secure";
108
109    /** Shared folder node name. */
110    public static final String N_SHARED_FOLDER = "shared-folder";
111
112    /** New secure modes node. */
113    public static final String N_OLD_STYLE_SECURE_SERVER = "oldStyleSecureServer";
114
115    /** The node name for the sites node. */
116    public static final String N_SITES = "sites";
117
118    /** The node name which indicates if apache should be configurable in sitemanager. */
119    public static final String N_WEBSERVERSCRIPTING = "webserver-scripting";
120
121    /** Configuration node name. */
122    public static final String N_WEBSERVERSCRIPTING_CONFIGTEMPLATE = "configtemplate";
123
124    /** Configuration node name. */
125    public static final String N_WEBSERVERSCRIPTING_FILENAMEPREFIX = "filenameprefix";
126
127    /** Configuration node name. */
128    public static final String N_WEBSERVERSCRIPTING_LOGGINGDIR = "loggingdir";
129
130    /** Configuration node name. */
131    public static final String N_WEBSERVERSCRIPTING_SECURETEMPLATE = "securetemplate";
132
133    /** Configuration node name. */
134    public static final String N_WEBSERVERSCRIPTING_TARGETPATH = "targetpath";
135
136    /** Configuration node name. */
137    public static final String N_WEBSERVERSCRIPTING_WEBSERVERSCRIPT = "webserverscript";
138
139    /** The node name for the workplace-server node. */
140    public static final String N_WORKPLACE_SERVER = "workplace-server";
141
142    /** The CmsObject with admin privileges. */
143    private CmsObject m_adminCms;
144
145    /** The configured site manager. */
146    private CmsSiteManagerImpl m_siteManager;
147
148    /** Future for the LetsEncrypt async update. */
149    private ScheduledFuture<?> m_updateFuture;
150
151    /**
152     * @see org.opencms.configuration.I_CmsXmlConfiguration#addXmlDigesterRules(org.apache.commons.digester3.Digester)
153     */
154    public void addXmlDigesterRules(Digester digester) {
155
156        // add site configuration rule
157        digester.addObjectCreate("*/" + N_SITES, CmsSiteManagerImpl.class);
158        digester.addCallMethod("*/" + N_SITES + "/" + N_WORKPLACE_SERVER, "addWorkplaceServer", 2);
159        digester.addCallParam("*/" + N_SITES + "/" + N_WORKPLACE_SERVER, 0);
160        digester.addCallParam("*/" + N_SITES + "/" + N_WORKPLACE_SERVER, 1, A_SSL);
161        digester.addCallMethod("*/" + N_SITES + "/" + N_DEFAULT_URI, "setDefaultUri", 0);
162        digester.addCallMethod("*/" + N_SITES + "/" + N_OLD_STYLE_SECURE_SERVER, "setOldStyleSecureServerAllowed", 0);
163
164        String configApachePath = "*/" + N_SITES + "/" + N_WEBSERVERSCRIPTING;
165        digester.addCallMethod(configApachePath, "setWebServerScripting", 6);
166        digester.addCallParam(configApachePath + "/" + N_WEBSERVERSCRIPTING_WEBSERVERSCRIPT, 0);
167        digester.addCallParam(configApachePath + "/" + N_WEBSERVERSCRIPTING_TARGETPATH, 1);
168        digester.addCallParam(configApachePath + "/" + N_WEBSERVERSCRIPTING_CONFIGTEMPLATE, 2);
169        digester.addCallParam(configApachePath + "/" + N_WEBSERVERSCRIPTING_SECURETEMPLATE, 3);
170        digester.addCallParam(configApachePath + "/" + N_WEBSERVERSCRIPTING_FILENAMEPREFIX, 4);
171        digester.addCallParam(configApachePath + "/" + N_WEBSERVERSCRIPTING_LOGGINGDIR, 5);
172
173        digester.addSetNext("*/" + N_SITES, "setSiteManager");
174
175        // add site configuration rule
176        String siteXpath = "*/" + N_SITES + "/" + N_SITE;
177
178        digester.addCallMethod(siteXpath, "addSiteInternally", 11);
179        digester.addCallParam(siteXpath, 0, A_SERVER);
180        digester.addCallParam(siteXpath, 1, A_URI);
181        digester.addCallParam(siteXpath, 2, A_TITLE);
182        digester.addCallParam(siteXpath, 3, A_POSITION);
183        digester.addCallParam(siteXpath, 4, A_ERROR_PAGE);
184        digester.addCallParam(siteXpath, 5, A_WEBSERVER);
185        digester.addCallParam(siteXpath, 6, A_SSL);
186        digester.addCallParam("*/" + N_SITES + "/" + N_SITE + "/" + N_SECURE, 7, A_SERVER);
187        digester.addCallParam("*/" + N_SITES + "/" + N_SITE + "/" + N_SECURE, 8, A_EXCLUSIVE);
188        digester.addCallParam("*/" + N_SITES + "/" + N_SITE + "/" + N_SECURE, 9, A_ERROR);
189        digester.addCallParam("*/" + N_SITES + "/" + N_SITE + "/" + N_SECURE, 10, A_USE_PERMANENT_REDIRECTS);
190        digester.addCallMethod(siteXpath + "/" + N_PARAMETERS + "/" + N_PARAM, "addParamToConfigSite", 2);
191        digester.addCallParam(siteXpath + "/" + N_PARAMETERS + "/" + N_PARAM, 0, A_NAME);
192        digester.addCallParam(siteXpath + "/" + N_PARAMETERS + "/" + N_PARAM, 1);
193        // add an alias to the currently configured site
194        digester.addCallMethod("*/" + N_SITES + "/" + N_SITE + "/" + N_ALIAS, "addAliasToConfigSite", 3);
195        digester.addCallParam("*/" + N_SITES + "/" + N_SITE + "/" + N_ALIAS, 0, A_SERVER);
196        digester.addCallParam("*/" + N_SITES + "/" + N_SITE + "/" + N_ALIAS, 1, A_REDIRECT);
197        digester.addCallParam("*/" + N_SITES + "/" + N_SITE + "/" + N_ALIAS, 2, A_OFFSET);
198
199        digester.addCallMethod("*/" + N_SITES + "/" + N_SHARED_FOLDER, "setSharedFolder", 0);
200
201    }
202
203    /**
204     * @see org.opencms.configuration.I_CmsXmlConfiguration#generateXml(org.dom4j.Element)
205     */
206    public Element generateXml(Element parent) {
207
208        // create <sites> node
209        Element sitesElement = parent.addElement(N_SITES);
210        if (OpenCms.getRunLevel() >= OpenCms.RUNLEVEL_3_SHELL_ACCESS) {
211            m_siteManager = OpenCms.getSiteManager();
212        }
213        Map<String, CmsSSLMode> workplaceMap = m_siteManager.getWorkplaceServersMap();
214        for (String server : workplaceMap.keySet()) {
215            Element workplaceElement = sitesElement.addElement(N_WORKPLACE_SERVER).addText(server);
216            workplaceElement.addAttribute(A_SSL, workplaceMap.get(server).getXMLValue());
217        }
218        sitesElement.addElement(N_DEFAULT_URI).addText(m_siteManager.getDefaultUri());
219        String sharedFolder = m_siteManager.getSharedFolder();
220        if (sharedFolder != null) {
221            sitesElement.addElement(N_SHARED_FOLDER).addText(sharedFolder);
222        }
223        String oldStyleSecureAllowed = String.valueOf(m_siteManager.isOldStyleSecureServerAllowed());
224        sitesElement.addElement(N_OLD_STYLE_SECURE_SERVER).addText(oldStyleSecureAllowed);
225        if (m_siteManager.isConfigurableWebServer()) {
226            Element configServer = sitesElement.addElement(N_WEBSERVERSCRIPTING);
227            Map<String, String> configServerMap = m_siteManager.getWebServerConfig();
228            configServer.addElement(N_WEBSERVERSCRIPTING_WEBSERVERSCRIPT).addText(
229                configServerMap.get(CmsSiteManagerImpl.WEB_SERVER_CONFIG_WEBSERVERSCRIPT));
230            configServer.addElement(N_WEBSERVERSCRIPTING_TARGETPATH).addText(
231                configServerMap.get(CmsSiteManagerImpl.WEB_SERVER_CONFIG_TARGETPATH));
232            configServer.addElement(N_WEBSERVERSCRIPTING_CONFIGTEMPLATE).addText(
233                configServerMap.get(CmsSiteManagerImpl.WEB_SERVER_CONFIG_CONFIGTEMPLATE));
234            configServer.addElement(N_WEBSERVERSCRIPTING_SECURETEMPLATE).addText(
235                configServerMap.get(CmsSiteManagerImpl.WEB_SERVER_CONFIG_SECURETEMPLATE));
236            configServer.addElement(N_WEBSERVERSCRIPTING_FILENAMEPREFIX).addText(
237                configServerMap.get(CmsSiteManagerImpl.WEB_SERVER_CONFIG_FILENAMEPREFIX));
238            configServer.addElement(N_WEBSERVERSCRIPTING_LOGGINGDIR).addText(
239                configServerMap.get(CmsSiteManagerImpl.WEB_SERVER_CONFIG_LOGGINGDIR));
240        }
241        Iterator<CmsSite> siteIterator = new HashSet<CmsSite>(m_siteManager.getSites().values()).iterator();
242        while (siteIterator.hasNext()) {
243            CmsSite site = siteIterator.next();
244            // create <site server="" uri=""/> subnode(s)
245            Element siteElement = sitesElement.addElement(N_SITE);
246
247            siteElement.addAttribute(A_SERVER, site.getSiteMatcher().toString());
248            siteElement.addAttribute(A_URI, site.getSiteRoot().concat("/"));
249            siteElement.addAttribute(A_TITLE, site.getTitle());
250            siteElement.addAttribute(A_POSITION, Float.toString(site.getPosition()));
251            siteElement.addAttribute(A_ERROR_PAGE, site.getErrorPage());
252            siteElement.addAttribute(A_WEBSERVER, String.valueOf(site.isWebserver()));
253            siteElement.addAttribute(A_SSL, site.getSSLMode().getXMLValue());
254
255            // create <secure server=""/> subnode
256            if (site.hasSecureServer()) {
257                Element secureElem = siteElement.addElement(N_SECURE);
258                secureElem.addAttribute(A_SERVER, site.getSecureUrl());
259
260                secureElem.addAttribute(A_EXCLUSIVE, String.valueOf(site.isExclusiveUrl()));
261                secureElem.addAttribute(A_ERROR, String.valueOf(site.isExclusiveError()));
262                if (site.usesPermanentRedirects()) {
263                    secureElem.addAttribute(A_USE_PERMANENT_REDIRECTS, Boolean.TRUE.toString());
264                }
265            }
266
267            if ((site.getParameters() != null) && !site.getParameters().isEmpty()) {
268                Element parametersElem = siteElement.addElement(N_PARAMETERS);
269                for (Map.Entry<String, String> entry : site.getParameters().entrySet()) {
270                    Element paramElem = parametersElem.addElement(N_PARAM);
271                    paramElem.addAttribute(A_NAME, entry.getKey());
272                    paramElem.addText(entry.getValue());
273                }
274            }
275
276            // create <alias server=""/> subnode(s)
277            Iterator<CmsSiteMatcher> aliasIterator = site.getAliases().iterator();
278            while (aliasIterator.hasNext()) {
279                CmsSiteMatcher matcher = aliasIterator.next();
280                Element aliasElement = siteElement.addElement(N_ALIAS);
281                aliasElement.addAttribute(A_SERVER, matcher.getUrl());
282                aliasElement.addAttribute(A_REDIRECT, String.valueOf(matcher.isRedirect()));
283                if (matcher.getTimeOffset() != 0) {
284                    aliasElement.addAttribute(A_OFFSET, "" + matcher.getTimeOffset());
285                }
286            }
287        }
288        return sitesElement;
289    }
290
291    /**
292     * @see org.opencms.configuration.I_CmsXmlConfiguration#getDtdFilename()
293     */
294    public String getDtdFilename() {
295
296        return CONFIGURATION_DTD_NAME;
297    }
298
299    /**
300     * Returns the site manager.<p>
301     *
302     * @return the site manager
303     */
304    public CmsSiteManagerImpl getSiteManager() {
305
306        return m_siteManager;
307    }
308
309    /**
310     * @see org.opencms.configuration.I_CmsXmlConfigurationWithUpdateHandler#handleUpdate()
311     */
312    public synchronized void handleUpdate() throws Exception {
313
314        CmsLetsEncryptConfiguration config = OpenCms.getLetsEncryptConfig();
315        if ((config != null) && config.isValidAndEnabled() && (config.getTrigger() == Trigger.siteConfig)) {
316
317            // the configuration may be written several times in quick succession. We want to update when this
318            // happens for the last time, not the first, so we use a scheduled task.
319
320            if (m_updateFuture != null) {
321                m_updateFuture.cancel(false);
322                m_updateFuture = null;
323            }
324            m_updateFuture = OpenCms.getExecutor().schedule(new Runnable() {
325
326                @SuppressWarnings("synthetic-access")
327                public void run() {
328
329                    m_updateFuture = null;
330                    CmsLogReport report = new CmsLogReport(
331                        Locale.ENGLISH,
332                        org.opencms.letsencrypt.CmsSiteConfigToLetsEncryptConfigConverter.class);
333                    CmsSiteConfigToLetsEncryptConfigConverter converter = new CmsSiteConfigToLetsEncryptConfigConverter(
334                        config);
335                    converter.run(report, OpenCms.getSiteManager());
336
337                    // TODO Auto-generated method stub
338
339                }
340            }, 5, TimeUnit.SECONDS);
341        }
342
343    }
344
345    /**
346     * @see org.opencms.configuration.I_CmsXmlConfigurationWithUpdateHandler#setCmsObject(org.opencms.file.CmsObject)
347     */
348    public void setCmsObject(CmsObject cms) {
349
350        m_adminCms = cms;
351    }
352
353    /**
354     * Sets the site manager.<p>
355     *
356     * @param siteManager the site manager to set
357     */
358    public void setSiteManager(CmsSiteManagerImpl siteManager) {
359
360        m_siteManager = siteManager;
361        if (CmsLog.INIT.isInfoEnabled()) {
362            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_SITE_CONFIG_FINISHED_0));
363        }
364    }
365
366    /**
367     * @see org.opencms.configuration.A_CmsXmlConfiguration#initMembers()
368     */
369    @Override
370    protected void initMembers() {
371
372        setXmlFileName(DEFAULT_XML_FILE_NAME);
373    }
374}