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.CmsAlternativeSiteRootMapping; 038import org.opencms.site.CmsSSLMode; 039import org.opencms.site.CmsSite; 040import org.opencms.site.CmsSiteManagerImpl; 041import org.opencms.site.CmsSiteMatcher; 042import org.opencms.site.CmsSiteMatcher.RedirectMode; 043 044import java.util.ArrayList; 045import java.util.HashSet; 046import java.util.Iterator; 047import java.util.List; 048import java.util.Locale; 049import java.util.Map; 050import java.util.Optional; 051import java.util.SortedMap; 052import java.util.TreeMap; 053import java.util.concurrent.ScheduledFuture; 054import java.util.concurrent.TimeUnit; 055 056import javax.xml.parsers.ParserConfigurationException; 057 058import org.apache.commons.digester3.CallMethodRule; 059import org.apache.commons.digester3.Digester; 060import org.apache.commons.digester3.NodeCreateRule; 061import org.apache.commons.digester3.ObjectCreateRule; 062import org.apache.commons.digester3.Rule; 063 064import org.dom4j.Element; 065import org.w3c.dom.NodeList; 066import org.xml.sax.Attributes; 067 068/** 069 * Class to read and write the OpenCms site configuration.<p> 070 */ 071public class CmsSitesConfiguration extends A_CmsXmlConfiguration implements I_CmsXmlConfigurationWithUpdateHandler { 072 073 /** The "error" attribute. */ 074 public static final String A_ERROR = "error"; 075 076 /** The "errorPage" attribute. */ 077 public static final String A_ERROR_PAGE = "errorPage"; 078 079 /** The "exclusive" attribute. */ 080 public static final String A_EXCLUSIVE = "exclusive"; 081 082 /** The attribute name for the alias offset. */ 083 public static final String A_OFFSET = "offset"; 084 085 /** The "position" attribute. */ 086 public static final String A_POSITION = "position"; 087 088 /** The "redirect" attribute. */ 089 public static final String A_REDIRECT = "redirect"; 090 091 /** The "server" attribute. */ 092 public static final String A_SERVER = "server"; 093 094 /** The ssl mode attribute.*/ 095 public static final String A_SSL = "sslmode"; 096 097 /** Attribute name for the subsiteSelection option. */ 098 public static final String A_SUBSITE_SELECTION = "subsiteSelection"; 099 100 /** The "title" attribute. */ 101 public static final String A_TITLE = "title"; 102 103 /** The "usePermanentRedirects" attribute. */ 104 public static final String A_USE_PERMANENT_REDIRECTS = "usePermanentRedirects"; 105 106 /** The "webserver" attribute. */ 107 public static final String A_WEBSERVER = "webserver"; 108 109 /** The name of the DTD for this configuration. */ 110 public static final String CONFIGURATION_DTD_NAME = "opencms-sites.dtd"; 111 112 /** The name of the default XML file for this configuration. */ 113 public static final String DEFAULT_XML_FILE_NAME = "opencms-sites.xml"; 114 115 /** The node name for the alias node. */ 116 public static final String N_ALIAS = "alias"; 117 118 /** The node name for the default-uri node. */ 119 public static final String N_DEFAULT_URI = "default-uri"; 120 121 /** New secure modes node. */ 122 public static final String N_OLD_STYLE_SECURE_SERVER = "oldStyleSecureServer"; 123 124 /** The node name for the parameters. */ 125 public static final String N_PARAMETERS = "parameters"; 126 127 /** The node name for the secure site. */ 128 public static final String N_SECURE = "secure"; 129 130 /** Shared folder node name. */ 131 public static final String N_SHARED_FOLDER = "shared-folder"; 132 133 /** The node name for the sites node. */ 134 public static final String N_SITES = "sites"; 135 136 /** The node name which indicates if apache should be configurable in sitemanager. */ 137 public static final String N_WEBSERVERSCRIPTING = "webserver-scripting"; 138 139 /** Configuration node name. */ 140 public static final String N_WEBSERVERSCRIPTING_CONFIGTEMPLATE = "configtemplate"; 141 142 /** Configuration node name. */ 143 public static final String N_WEBSERVERSCRIPTING_FILENAMEPREFIX = "filenameprefix"; 144 145 /** Configuration node name. */ 146 public static final String N_WEBSERVERSCRIPTING_LOGGINGDIR = "loggingdir"; 147 148 /** Configuration node name. */ 149 public static final String N_WEBSERVERSCRIPTING_SECURETEMPLATE = "securetemplate"; 150 151 /** Configuration node name. */ 152 public static final String N_WEBSERVERSCRIPTING_TARGETPATH = "targetpath"; 153 154 /** Configuration node name. */ 155 public static final String N_WEBSERVERSCRIPTING_WEBSERVERSCRIPT = "webserverscript"; 156 157 /** The node name for the workplace-server node. */ 158 public static final String N_WORKPLACE_SERVER = "workplace-server"; 159 160 /** The CmsObject with admin privileges. */ 161 private CmsObject m_adminCms; 162 163 /** The configured site manager. */ 164 private CmsSiteManagerImpl m_siteManager; 165 166 /** Future for the LetsEncrypt async update. */ 167 private ScheduledFuture<?> m_updateFuture; 168 169 /** 170 * @see org.opencms.configuration.I_CmsXmlConfiguration#addXmlDigesterRules(org.apache.commons.digester3.Digester) 171 */ 172 public void addXmlDigesterRules(Digester digester) { 173 174 // add site configuration rule 175 digester.addObjectCreate("*/" + N_SITES, CmsSiteManagerImpl.class); 176 digester.addCallMethod("*/" + N_SITES + "/" + N_WORKPLACE_SERVER, "addWorkplaceServer", 2); 177 digester.addCallParam("*/" + N_SITES + "/" + N_WORKPLACE_SERVER, 0); 178 digester.addCallParam("*/" + N_SITES + "/" + N_WORKPLACE_SERVER, 1, A_SSL); 179 digester.addCallMethod("*/" + N_SITES + "/" + N_DEFAULT_URI, "setDefaultUri", 0); 180 digester.addCallMethod("*/" + N_SITES + "/" + N_OLD_STYLE_SECURE_SERVER, "setOldStyleSecureServerAllowed", 0); 181 182 String configApachePath = "*/" + N_SITES + "/" + N_WEBSERVERSCRIPTING; 183 digester.addCallMethod(configApachePath, "setWebServerScripting", 6); 184 digester.addCallParam(configApachePath + "/" + N_WEBSERVERSCRIPTING_WEBSERVERSCRIPT, 0); 185 digester.addCallParam(configApachePath + "/" + N_WEBSERVERSCRIPTING_TARGETPATH, 1); 186 digester.addCallParam(configApachePath + "/" + N_WEBSERVERSCRIPTING_CONFIGTEMPLATE, 2); 187 digester.addCallParam(configApachePath + "/" + N_WEBSERVERSCRIPTING_SECURETEMPLATE, 3); 188 digester.addCallParam(configApachePath + "/" + N_WEBSERVERSCRIPTING_FILENAMEPREFIX, 4); 189 digester.addCallParam(configApachePath + "/" + N_WEBSERVERSCRIPTING_LOGGINGDIR, 5); 190 191 digester.addSetNext("*/" + N_SITES, "setSiteManager"); 192 193 // add site configuration rule 194 String siteXpath = "*/" + N_SITES + "/" + N_SITE; 195 digester.addRule( 196 siteXpath, 197 new CallMethodRule( 198 "addSiteInternally", 199 15, 200 new Class[] { 201 String.class, 202 String.class, 203 String.class, 204 String.class, 205 String.class, 206 String.class, 207 String.class, 208 String.class, 209 String.class, 210 String.class, 211 String.class, 212 String.class, 213 SortedMap.class, 214 List.class, 215 Optional.class}) { 216 217 @Override 218 public void begin(String namespace, String name, Attributes attributes) throws Exception { 219 220 super.begin(namespace, name, attributes); 221 getDigester().peekParams()[12] = new TreeMap(); 222 getDigester().peekParams()[13] = new ArrayList(); 223 getDigester().peekParams()[14] = Optional.empty(); // non-string parameters must be initialized to a non-null value, so we have to use Optional 224 } 225 }); 226 digester.addCallParam(siteXpath, 0, A_SERVER); 227 digester.addCallParam(siteXpath, 1, A_URI); 228 digester.addCallParam(siteXpath, 2, A_TITLE); 229 digester.addCallParam(siteXpath, 3, A_POSITION); 230 digester.addCallParam(siteXpath, 4, A_ERROR_PAGE); 231 digester.addCallParam(siteXpath, 5, A_WEBSERVER); 232 digester.addCallParam(siteXpath, 6, A_SSL); 233 digester.addCallParam("*/" + N_SITES + "/" + N_SITE + "/" + N_SECURE, 7, A_SERVER); 234 digester.addCallParam("*/" + N_SITES + "/" + N_SITE + "/" + N_SECURE, 8, A_EXCLUSIVE); 235 digester.addCallParam("*/" + N_SITES + "/" + N_SITE + "/" + N_SECURE, 9, A_ERROR); 236 digester.addCallParam("*/" + N_SITES + "/" + N_SITE + "/" + N_SECURE, 10, A_USE_PERMANENT_REDIRECTS); 237 digester.addCallParam(siteXpath, 11, A_SUBSITE_SELECTION); 238 239 digester.addRule(siteXpath + "/" + N_PARAMETERS, new ObjectCreateRule(TreeMap.class) { 240 241 @Override 242 public void end(String namespace, String name) throws Exception { 243 244 getDigester().peekParams()[12] = getDigester().peek(); 245 super.end(namespace, name); 246 } 247 }); 248 digester.addCallMethod(siteXpath + "/" + N_PARAMETERS + "/" + N_PARAM, "put", 2); 249 digester.addCallParam(siteXpath + "/" + N_PARAMETERS + "/" + N_PARAM, 0, A_NAME); 250 digester.addCallParam(siteXpath + "/" + N_PARAMETERS + "/" + N_PARAM, 1); 251 252 digester.addRule("*/" + N_SITES + "/" + N_SITE + "/" + N_ALIAS, new Rule() { 253 254 @Override 255 public void begin(String namespace, String name, Attributes attributes) throws Exception { 256 257 String server = attributes.getValue(A_SERVER); 258 String redirect = attributes.getValue(A_REDIRECT); 259 String offset = attributes.getValue(A_OFFSET); 260 CmsSiteMatcher matcher = CmsSiteManagerImpl.createAliasSiteMatcher(server, redirect, offset); 261 Object[] params = getDigester().peekParams(); 262 ((ArrayList)params[13]).add(matcher); 263 264 } 265 }); 266 267 try { 268 digester.addRule( 269 "*/" + N_SITES + "/" + N_SITE + "/" + CmsAlternativeSiteRootMapping.N_ALTERNATIVE_SITE_ROOT_MAPPING, 270 new NodeCreateRule() { 271 272 @Override 273 public void end(String namespace, String name) throws Exception { 274 275 org.w3c.dom.Element elem = (org.w3c.dom.Element)digester.peek(); 276 String uri = elem.getAttribute(I_CmsXmlConfiguration.A_URI); 277 String titlePrefix = elem.getAttribute(CmsAlternativeSiteRootMapping.A_TITLE_SUFFIX); 278 NodeList nodes = elem.getElementsByTagName(CmsAlternativeSiteRootMapping.N_PATH); 279 List<String> paths = new ArrayList<>(); 280 for (int i = 0; i < nodes.getLength(); i++) { 281 org.w3c.dom.Element pathElem = (org.w3c.dom.Element)nodes.item(i); 282 String path = pathElem.getTextContent().trim(); 283 paths.add(path); 284 } 285 CmsAlternativeSiteRootMapping mapping = new CmsAlternativeSiteRootMapping( 286 uri, 287 paths, 288 titlePrefix); 289 getDigester().peekParams()[14] = Optional.of(mapping); 290 super.end(namespace, name); 291 } 292 293 }); 294 } catch (ParserConfigurationException e) { 295 throw new RuntimeException(e); 296 } 297 digester.addCallMethod("*/" + N_SITES + "/" + N_SHARED_FOLDER, "setSharedFolder", 0); 298 299 } 300 301 /** 302 * @see org.opencms.configuration.I_CmsXmlConfiguration#generateXml(org.dom4j.Element) 303 */ 304 public Element generateXml(Element parent) { 305 306 // create <sites> node 307 Element sitesElement = parent.addElement(N_SITES); 308 if (OpenCms.getRunLevel() >= OpenCms.RUNLEVEL_3_SHELL_ACCESS) { 309 m_siteManager = OpenCms.getSiteManager(); 310 } 311 Map<String, CmsSSLMode> workplaceMap = m_siteManager.getWorkplaceServersMap(); 312 for (String server : workplaceMap.keySet()) { 313 Element workplaceElement = sitesElement.addElement(N_WORKPLACE_SERVER).addText(server); 314 workplaceElement.addAttribute(A_SSL, workplaceMap.get(server).getXMLValue()); 315 } 316 sitesElement.addElement(N_DEFAULT_URI).addText(m_siteManager.getDefaultUri()); 317 String sharedFolder = m_siteManager.getSharedFolder(); 318 if (sharedFolder != null) { 319 sitesElement.addElement(N_SHARED_FOLDER).addText(sharedFolder); 320 } 321 String oldStyleSecureAllowed = String.valueOf(m_siteManager.isOldStyleSecureServerAllowed()); 322 sitesElement.addElement(N_OLD_STYLE_SECURE_SERVER).addText(oldStyleSecureAllowed); 323 if (m_siteManager.isConfigurableWebServer()) { 324 Element configServer = sitesElement.addElement(N_WEBSERVERSCRIPTING); 325 Map<String, String> configServerMap = m_siteManager.getWebServerConfig(); 326 configServer.addElement(N_WEBSERVERSCRIPTING_WEBSERVERSCRIPT).addText( 327 configServerMap.get(CmsSiteManagerImpl.WEB_SERVER_CONFIG_WEBSERVERSCRIPT)); 328 configServer.addElement(N_WEBSERVERSCRIPTING_TARGETPATH).addText( 329 configServerMap.get(CmsSiteManagerImpl.WEB_SERVER_CONFIG_TARGETPATH)); 330 configServer.addElement(N_WEBSERVERSCRIPTING_CONFIGTEMPLATE).addText( 331 configServerMap.get(CmsSiteManagerImpl.WEB_SERVER_CONFIG_CONFIGTEMPLATE)); 332 configServer.addElement(N_WEBSERVERSCRIPTING_SECURETEMPLATE).addText( 333 configServerMap.get(CmsSiteManagerImpl.WEB_SERVER_CONFIG_SECURETEMPLATE)); 334 configServer.addElement(N_WEBSERVERSCRIPTING_FILENAMEPREFIX).addText( 335 configServerMap.get(CmsSiteManagerImpl.WEB_SERVER_CONFIG_FILENAMEPREFIX)); 336 configServer.addElement(N_WEBSERVERSCRIPTING_LOGGINGDIR).addText( 337 configServerMap.get(CmsSiteManagerImpl.WEB_SERVER_CONFIG_LOGGINGDIR)); 338 } 339 Iterator<CmsSite> siteIterator = new HashSet<CmsSite>(m_siteManager.getSites().values()).iterator(); 340 while (siteIterator.hasNext()) { 341 CmsSite site = siteIterator.next(); 342 // create <site server="" uri=""/> subnode(s) 343 Element siteElement = sitesElement.addElement(N_SITE); 344 345 siteElement.addAttribute(A_SERVER, site.getSiteMatcher().toString()); 346 siteElement.addAttribute(A_URI, site.getSiteRoot().concat("/")); 347 siteElement.addAttribute(A_TITLE, site.getTitle()); 348 siteElement.addAttribute(A_POSITION, Float.toString(site.getPosition())); 349 siteElement.addAttribute(A_ERROR_PAGE, site.getErrorPage()); 350 siteElement.addAttribute(A_WEBSERVER, String.valueOf(site.isWebserver())); 351 siteElement.addAttribute(A_SSL, site.getSSLMode().getXMLValue()); 352 siteElement.addAttribute(A_SUBSITE_SELECTION, "" + site.isSubsiteSelectionEnabled()); 353 354 // create <secure server=""/> subnode 355 if (site.hasSecureServer()) { 356 Element secureElem = siteElement.addElement(N_SECURE); 357 secureElem.addAttribute(A_SERVER, site.getSecureUrl()); 358 359 secureElem.addAttribute(A_EXCLUSIVE, String.valueOf(site.isExclusiveUrl())); 360 secureElem.addAttribute(A_ERROR, String.valueOf(site.isExclusiveError())); 361 if (site.usesPermanentRedirects()) { 362 secureElem.addAttribute(A_USE_PERMANENT_REDIRECTS, Boolean.TRUE.toString()); 363 } 364 } 365 366 if ((site.getParameters() != null) && !site.getParameters().isEmpty()) { 367 Element parametersElem = siteElement.addElement(N_PARAMETERS); 368 for (Map.Entry<String, String> entry : site.getParameters().entrySet()) { 369 Element paramElem = parametersElem.addElement(N_PARAM); 370 paramElem.addAttribute(A_NAME, entry.getKey()); 371 paramElem.addText(entry.getValue()); 372 } 373 } 374 375 // create <alias server=""/> subnode(s) 376 Iterator<CmsSiteMatcher> aliasIterator = site.getAliases().iterator(); 377 while (aliasIterator.hasNext()) { 378 CmsSiteMatcher matcher = aliasIterator.next(); 379 Element aliasElement = siteElement.addElement(N_ALIAS); 380 aliasElement.addAttribute(A_SERVER, matcher.getUrl()); 381 382 RedirectMode redirectMode = matcher.getRedirectMode(); 383 384 String redirectModeStr = null; 385 switch (redirectMode) { 386 387 case permanent: 388 redirectModeStr = "permanent"; 389 break; 390 case temporary: 391 392 redirectModeStr = "true"; 393 break; 394 case none: 395 default: 396 redirectModeStr = "false"; 397 break; 398 } 399 aliasElement.addAttribute(A_REDIRECT, redirectModeStr); 400 401 if (matcher.getTimeOffset() != 0) { 402 aliasElement.addAttribute(A_OFFSET, "" + (matcher.getTimeOffset() / 1000)); 403 } 404 } 405 java.util.Optional<CmsAlternativeSiteRootMapping> altSiteRoot = site.getAlternativeSiteRootMapping(); 406 if (altSiteRoot.isPresent()) { 407 altSiteRoot.get().appendXml(siteElement); 408 } 409 } 410 return sitesElement; 411 412 } 413 414 /** 415 * @see org.opencms.configuration.I_CmsXmlConfiguration#getDtdFilename() 416 */ 417 public String getDtdFilename() { 418 419 return CONFIGURATION_DTD_NAME; 420 } 421 422 /** 423 * Returns the site manager.<p> 424 * 425 * @return the site manager 426 */ 427 public CmsSiteManagerImpl getSiteManager() { 428 429 return m_siteManager; 430 } 431 432 /** 433 * @see org.opencms.configuration.I_CmsXmlConfigurationWithUpdateHandler#handleUpdate() 434 */ 435 public synchronized void handleUpdate() throws Exception { 436 437 CmsLetsEncryptConfiguration config = OpenCms.getLetsEncryptConfig(); 438 if ((config != null) && config.isValidAndEnabled() && (config.getTrigger() == Trigger.siteConfig)) { 439 440 // the configuration may be written several times in quick succession. We want to update when this 441 // happens for the last time, not the first, so we use a scheduled task. 442 443 if (m_updateFuture != null) { 444 m_updateFuture.cancel(false); 445 m_updateFuture = null; 446 } 447 m_updateFuture = OpenCms.getExecutor().schedule(new Runnable() { 448 449 @SuppressWarnings("synthetic-access") 450 public void run() { 451 452 m_updateFuture = null; 453 CmsLogReport report = new CmsLogReport( 454 Locale.ENGLISH, 455 org.opencms.letsencrypt.CmsSiteConfigToLetsEncryptConfigConverter.class); 456 CmsSiteConfigToLetsEncryptConfigConverter converter = new CmsSiteConfigToLetsEncryptConfigConverter( 457 config); 458 converter.run(report, OpenCms.getSiteManager()); 459 460 // TODO Auto-generated method stub 461 462 } 463 }, 5, TimeUnit.SECONDS); 464 } 465 466 } 467 468 /** 469 * @see org.opencms.configuration.I_CmsXmlConfigurationWithUpdateHandler#setCmsObject(org.opencms.file.CmsObject) 470 */ 471 public void setCmsObject(CmsObject cms) { 472 473 m_adminCms = cms; 474 } 475 476 /** 477 * Sets the site manager.<p> 478 * 479 * @param siteManager the site manager to set 480 */ 481 public void setSiteManager(CmsSiteManagerImpl siteManager) { 482 483 m_siteManager = siteManager; 484 if (CmsLog.INIT.isInfoEnabled()) { 485 CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_SITE_CONFIG_FINISHED_0)); 486 } 487 } 488 489 /** 490 * @see org.opencms.configuration.A_CmsXmlConfiguration#initMembers() 491 */ 492 @Override 493 protected void initMembers() { 494 495 setXmlFileName(DEFAULT_XML_FILE_NAME); 496 } 497}