diff --git a/i18n/src/main/resources/openfire_i18n.properties b/i18n/src/main/resources/openfire_i18n.properties index 865d2faff6..dc00bd5111 100644 --- a/i18n/src/main/resources/openfire_i18n.properties +++ b/i18n/src/main/resources/openfire_i18n.properties @@ -1715,6 +1715,10 @@ system_property.adminConsole.forwarded.for.header=The HTTP header name for 'forw system_property.adminConsole.forwarded.server.header=The HTTP header name for 'forwarded server'. system_property.adminConsole.forwarded.host.header=The HTTP header name for 'forwarded hosts'. system_property.adminConsole.forwarded.host.name=Sets a forced valued for the host header. +system_property.adminConsole.maxAttemptsPerIP=Maximum number of Admin Console login attempts per IP address that can be performed in a given time frame. +system_property.adminConsole.perIPAttemptResetInterval=Time frame before Admin Console login attempts per IP address are reset. +system_property.adminConsole.maxAttemptsPerUsername=Maximum number of Admin Console login attempts per username that can be performed in a given time frame. +system_property.adminConsole.perUsernameAttemptResetInterval=Time frame before Admin Console login attempts per username are reset. system_property.xmpp.muc.muclumbus.v1-0.enabled=Determine is the multi-user chat "muclumbus" (v1.0) search feature is enabled. system_property.xmpp.muc.join.presence=Setting the presence send of participants joining in MUC rooms. system_property.xmpp.muc.join.self-presence-timeout=Maximum duration to wait for presence to be broadcast while joining a MUC room. diff --git a/i18n/src/main/resources/openfire_i18n_nl.properties b/i18n/src/main/resources/openfire_i18n_nl.properties index d86e79743e..413da81f25 100644 --- a/i18n/src/main/resources/openfire_i18n_nl.properties +++ b/i18n/src/main/resources/openfire_i18n_nl.properties @@ -1634,6 +1634,10 @@ system_property.adminConsole.forwarded.for.header=De naam van de HTTP header voo system_property.adminConsole.forwarded.server.header=De naam van de HTTP header voor 'forwarded server'. system_property.adminConsole.forwarded.host.header=De naam van de HTTP header voor 'forwarded hosts'. system_property.adminConsole.forwarded.host.name=Zet een gefoceerde waarde voor de host header. +system_property.adminConsole.maxAttemptsPerIP=Maximaal aantal inlogpoginging voor de beheerconsole, per IP, in een bepaald tijdsbestek. +system_property.adminConsole.perIPAttemptResetInterval=Tijdsbestek waarna inlogpogingen voor de beheerconsole, per IP, worden gereset. +system_property.adminConsole.maxAttemptsPerUsername=Maximaal aantal inlogpoginging voor de beheerconsole, per gebruikersnaam, in een bepaald tijdsbestek. +system_property.adminConsole.perUsernameAttemptResetInterval=Tijdsbestek waarna inlogpogingen voor de beheerconsole, per gebruikersnaam, worden gereset. system_property.xmpp.muc.muclumbus.v1-0.enabled=Definieert of de multi-user chat "muclumbus" (v1.0) zoekfunctionaliteit wordt geactiveerd. system_property.xmpp.muc.join.presence=Wissel statusinformatie uit als een nieuwe participant een MUC ruimte binnentreedt. system_property.xmpp.muc.join.self-presence-timeout=Maximale wachttijd voor omgeroepen status informatie tijdens het binnentreden van een MUC ruimte. diff --git a/xmppserver/src/main/java/org/jivesoftware/admin/LoginLimitManager.java b/xmppserver/src/main/java/org/jivesoftware/admin/LoginLimitManager.java index 8ebc924ff3..1936f45c52 100644 --- a/xmppserver/src/main/java/org/jivesoftware/admin/LoginLimitManager.java +++ b/xmppserver/src/main/java/org/jivesoftware/admin/LoginLimitManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004-2009 Jive Software, 2022 Ignite Realtime Foundation. All rights reserved. + * Copyright (C) 2004-2009 Jive Software, 2022-2023 Ignite Realtime Foundation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,13 @@ package org.jivesoftware.admin; import org.jivesoftware.openfire.security.SecurityAuditManager; -import org.jivesoftware.util.JiveGlobals; +import org.jivesoftware.util.SystemProperty; import org.jivesoftware.util.TaskEngine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; +import java.time.temporal.ChronoUnit; import java.util.Map; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; @@ -52,14 +53,38 @@ public static LoginLimitManager getInstance() { } // Max number of attempts per ip address that can be performed in given time frame - private long maxAttemptsPerIP; + public static final SystemProperty MAX_ATTEMPTS_PER_IP = SystemProperty.Builder.ofType(Long.class) + .setKey("adminConsole.maxAttemptsPerIP") + .setDynamic(true) + .setDefaultValue(10L) + .setMinValue(1L) + .build(); + // Time frame before attempts per ip addresses are reset - private Duration millisecondsBetweenPerIP; + public static final SystemProperty PER_IP_ATTEMPT_RESET_INTERVAL = SystemProperty.Builder.ofType(Duration.class) + .setKey("adminConsole.perIPAttemptResetInterval") + .setDynamic(false) + .setChronoUnit(ChronoUnit.MILLIS) + .setDefaultValue(Duration.ofMinutes(15)) + .setMinValue(Duration.ofMillis(1)) + .build(); // Max number of attempts per username that can be performed in a given time frame - private long maxAttemptsPerUsername; + public static final SystemProperty MAX_ATTEMPTS_PER_USERNAME = SystemProperty.Builder.ofType(Long.class) + .setKey("adminConsole.maxAttemptsPerUsername") + .setDynamic(true) + .setDefaultValue(10L) + .setMinValue(1L) + .build(); + // Time frame before attempts per username are reset - private Duration millisecondsBetweenPerUsername; + public static final SystemProperty PER_USERNAME_ATTEMPT_RESET_INTERVAL = SystemProperty.Builder.ofType(Duration.class) + .setKey("adminConsole.perUsernameAttemptResetInterval") + .setDynamic(false) + .setChronoUnit(ChronoUnit.MILLIS) + .setDefaultValue(Duration.ofMinutes(15)) + .setMinValue(Duration.ofMillis(1)) + .build(); // Record of attempts per IP address private Map attemptsPerIP; @@ -82,18 +107,10 @@ private LoginLimitManager() { attemptsPerIP = new ConcurrentHashMap<>(); attemptsPerUsername = new ConcurrentHashMap<>(); - // Max number of attempts per ip address that can be performed in given time frame (10 attempts default) - maxAttemptsPerIP = JiveGlobals.getLongProperty("adminConsole.maxAttemptsPerIP", 10); - // Time frame before attempts per ip addresses are reset (15 minutes default) - millisecondsBetweenPerIP = Duration.ofMillis(JiveGlobals.getLongProperty("adminConsole.perIPAttemptResetInterval", 900000)); - // Max number of attempts per username that can be performed in a given time frame (10 attempts default) - maxAttemptsPerUsername = JiveGlobals.getLongProperty("adminConsole.maxAttemptsPerUsername", 10); - // Time frame before attempts per ip addresses are reset (15 minutes default) - millisecondsBetweenPerUsername = Duration.ofMillis(JiveGlobals.getLongProperty("adminConsole.perUsernameAttemptResetInterval", 900000)); // Set up per username attempt reset task - taskEngine.scheduleAtFixedRate(new PerUsernameTask(), Duration.ZERO, millisecondsBetweenPerUsername); + taskEngine.scheduleAtFixedRate(new PerUsernameTask(), Duration.ZERO, PER_USERNAME_ATTEMPT_RESET_INTERVAL.getValue()); // Set up per IP attempt reset task - taskEngine.scheduleAtFixedRate(new PerIPAddressTask(), Duration.ZERO, millisecondsBetweenPerIP); + taskEngine.scheduleAtFixedRate(new PerIPAddressTask(), Duration.ZERO, PER_IP_ATTEMPT_RESET_INTERVAL.getValue()); } /** @@ -104,10 +121,10 @@ private LoginLimitManager() { * @return True if the login attempt limit has been hit. */ public boolean hasHitConnectionLimit(String username, String address) { - if (attemptsPerIP.get(address) != null && attemptsPerIP.get(address) > maxAttemptsPerIP) { + if (attemptsPerIP.get(address) != null && attemptsPerIP.get(address) > MAX_ATTEMPTS_PER_IP.getValue()) { return true; } - if (attemptsPerUsername.get(username) != null && attemptsPerUsername.get(username) > maxAttemptsPerUsername) { + if (attemptsPerUsername.get(username) != null && attemptsPerUsername.get(username) > MAX_ATTEMPTS_PER_USERNAME.getValue()) { return true; } // No problem then, no limit hit. @@ -130,7 +147,7 @@ public void recordFailedAttempt(String username, String address) { cnt++; attemptsPerIP.put(address, cnt); final StringBuilder sb = new StringBuilder(); - if (cnt > maxAttemptsPerIP) { + if (cnt > MAX_ATTEMPTS_PER_IP.getValue()) { Log.warn("Login attempt limit breached for address "+address); sb.append("Future login attempts from this address will be temporarily locked out. "); } @@ -141,7 +158,7 @@ public void recordFailedAttempt(String username, String address) { } cnt++; attemptsPerUsername.put(username, cnt); - if (cnt > maxAttemptsPerUsername) { + if (cnt > MAX_ATTEMPTS_PER_USERNAME.getValue()) { Log.warn("Login attempt limit breached for username "+username); sb.append("Future login attempts for this user will be temporarily locked out. "); } diff --git a/xmppserver/src/main/java/org/jivesoftware/admin/servlet/SystemPropertiesServlet.java b/xmppserver/src/main/java/org/jivesoftware/admin/servlet/SystemPropertiesServlet.java index e7c32e3a7a..86018cb9eb 100644 --- a/xmppserver/src/main/java/org/jivesoftware/admin/servlet/SystemPropertiesServlet.java +++ b/xmppserver/src/main/java/org/jivesoftware/admin/servlet/SystemPropertiesServlet.java @@ -130,7 +130,7 @@ private void saveProperty(final HttpServletRequest request, final WebManager web final boolean oldEncrypt = JiveGlobals.isPropertyEncrypted(key); final String oldValueToLog = oldEncrypt ? "***********" : JiveGlobals.getProperty(key); final String value = request.getParameter("value"); - final boolean encrypt = ParamUtils.getBooleanAttribute(request, "encrypt"); + final boolean encrypt = ParamUtils.getBooleanParameter(request, "encrypt"); final boolean alreadyExists = JiveGlobals.getProperty(key) != null; JiveGlobals.setProperty(key, value, encrypt); request.getSession().setAttribute("successMessage", diff --git a/xmppserver/src/main/java/org/jivesoftware/openfire/container/PluginMonitor.java b/xmppserver/src/main/java/org/jivesoftware/openfire/container/PluginMonitor.java index a9339b9f57..db74c1d048 100644 --- a/xmppserver/src/main/java/org/jivesoftware/openfire/container/PluginMonitor.java +++ b/xmppserver/src/main/java/org/jivesoftware/openfire/container/PluginMonitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 IgniteRealtime.org + * Copyright 2016-2023 Ignite Realtime Foundation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -350,68 +350,67 @@ public boolean accept( final Path path ) throws IOException // plugins always precede their children. final Deque> dirs = sortPluginDirs( ds, devPlugins ); - // Hierarchy processing could be parallel. - final Collection> parallelProcesses = new ArrayList<>(); - for ( final List hierarchy : dirs ) + // Before running any plugin, make sure that the admin plugin is loaded. It is a dependency + // of all plugins that attempt to modify the admin panel. + if ( pluginManager.getPlugin( "admin" ) == null ) { - parallelProcesses.add( new Callable() - { + pluginManager.loadPlugin( "admin", dirs.getFirst().get( 0 ) ); + } - @Override - public Integer call() throws Exception + // Prevent trying to read properties from the database while we're still in setup mode. + if (!XMPPServer.getInstance().isSetupMode()) + { + // Hierarchy processing could be parallel. + final Collection> parallelProcesses = new ArrayList<>(); + for ( final List hierarchy : dirs ) + { + parallelProcesses.add( new Callable() { - int loaded = 0; - for ( final Path path : hierarchy ) + + @Override + public Integer call() throws Exception { - // If the plugin hasn't already been started, start it. - final String canonicalName = PluginMetadataHelper.getCanonicalName( path ); - if ( pluginManager.getPlugin( canonicalName ) == null ) + int loaded = 0; + for ( final Path path : hierarchy ) { - if ( pluginManager.loadPlugin( canonicalName, path ) ) + // If the plugin hasn't already been started, start it. + final String canonicalName = PluginMetadataHelper.getCanonicalName( path ); + if ( pluginManager.getPlugin( canonicalName ) == null ) { - loaded++; + if ( pluginManager.loadPlugin( canonicalName, path ) ) + { + loaded++; + } } } + + return loaded; } + } ); + } - return loaded; + // Hierarchies could be processed in parallel. This is likely to be beneficial during the first + // execution of this monitor, as during later executions, most plugins will likely already be loaded. + final int parallelProcessMax = JiveGlobals.getIntProperty("plugins.loading.max-parallel", 4); + final int parallelProcessCount = (pluginManager.isExecuted() ? 1 : parallelProcessMax); + final ThreadFactory threadFactory = new NamedThreadFactory("PluginMonitorExec-", Executors.defaultThreadFactory(), false, Thread.NORM_PRIORITY); + final ExecutorService executorService = Executors.newFixedThreadPool(parallelProcessCount, threadFactory); + try { + // Blocks until ready + final List> futures = executorService.invokeAll(parallelProcesses); + + // Unless nothing happened, report that we're done loading plugins. + int pluginsLoaded = 0; + for (Future future : futures) { + pluginsLoaded += future.get(); } - } ); - } - - // Before running any plugin, make sure that the admin plugin is loaded. It is a dependency - // of all plugins that attempt to modify the admin panel. - if ( pluginManager.getPlugin( "admin" ) == null ) - { - pluginManager.loadPlugin( "admin", dirs.getFirst().get( 0 ) ); - } - - // Hierarchies could be processed in parallel. This is likely to be beneficial during the first - // execution of this monitor, as during later executions, most plugins will likely already be loaded. - final int parallelProcessMax = JiveGlobals.getIntProperty( "plugins.loading.max-parallel", 4 ); - final int parallelProcessCount = ( pluginManager.isExecuted() ? 1 : parallelProcessMax ); - final ThreadFactory threadFactory = new NamedThreadFactory("PluginMonitorExec-", Executors.defaultThreadFactory(), false, Thread.NORM_PRIORITY); - final ExecutorService executorService = Executors.newFixedThreadPool( parallelProcessCount, threadFactory ); - try - { - // Blocks until ready - final List> futures = executorService.invokeAll( parallelProcesses ); - - // Unless nothing happened, report that we're done loading plugins. - int pluginsLoaded = 0; - for ( Future future : futures ) - { - pluginsLoaded += future.get(); - } - if ( pluginsLoaded > 0 && !XMPPServer.getInstance().isSetupMode() ) - { - Log.info( "Finished processing all plugins." ); + if (pluginsLoaded > 0) { + Log.info("Finished processing all plugins."); + } + } finally { + executorService.shutdown(); } } - finally - { - executorService.shutdown(); - } // Trigger event that plugins have been monitored pluginManager.firePluginsMonitored(); diff --git a/xmppserver/src/main/java/org/jivesoftware/openfire/nio/XMLLightweightParser.java b/xmppserver/src/main/java/org/jivesoftware/openfire/nio/XMLLightweightParser.java index 13599cdfb2..6d9a9e8d06 100644 --- a/xmppserver/src/main/java/org/jivesoftware/openfire/nio/XMLLightweightParser.java +++ b/xmppserver/src/main/java/org/jivesoftware/openfire/nio/XMLLightweightParser.java @@ -23,8 +23,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * This is a Light-Weight XML Parser. @@ -38,9 +36,6 @@ * @author Gaston Dombiak */ class XMLLightweightParser { - - private static final Pattern XML_HAS_CHARREF = Pattern.compile("&#(0*([0-9]+)|[xX]0*([0-9a-fA-F]+));"); - private static final String MAX_PROPERTY_NAME = "xmpp.parser.buffer.size"; private static int maxBufferSize; // Chars that represent CDATA section start @@ -377,37 +372,41 @@ else if (ch == '/' && head.length() > 0) { * The input string * @return {@code true} if the input string contains an invalid numeric character reference, {@code false} * otherwise. - * @see http://www.w3.org/TR/2008/REC-xml-20081126/#dt-charref + * @see Definition of a character reference */ public static boolean hasIllegalCharacterReferences(String string) { - // If there's no character reference, don't bother to do more specific checking. - final Matcher matcher = XML_HAS_CHARREF.matcher(string); - - while (matcher.find()) { - final String decValue = matcher.group(2); - if (decValue != null) { - final int value = Integer.parseInt(decValue); - if (!isLegalXmlCharacter(value)) { - return true; - } else { - continue; - } + int needle = 0; + while (needle < string.length()) { + final int start = string.indexOf("&#", needle); + if (start == -1) { + return false; + } + final int end = string.indexOf(";", start + 2); + if (end == -1) { + return false; + } + needle = end; + + final boolean isHex = string.charAt(start + 2) == 'x' || string.charAt(start + 2) == 'X'; + + final String candidate; + final int radix; + if (isHex) { + candidate = string.substring(start + 3, end); + radix = 16; + } else { + candidate = string.substring(start + 2, end); + radix = 10; } - final String hexValue = matcher.group(3); - if (hexValue != null) { - final int value = Integer.parseInt(hexValue, 16); + try { + final int value = Integer.parseInt(candidate, radix); if (!isLegalXmlCharacter(value)) { return true; - } else { - continue; } + } catch (NumberFormatException e) { + // The 'candidate' value wasn't a numeric character reference. } - - // This is bad. The XML_HAS_CHARREF expression should have a hit for either the decimal - // or the heximal notation. - throw new IllegalStateException( - "An error occurred while searching for illegal character references in the value [" + string + "]."); } return false; @@ -419,8 +418,9 @@ public static boolean hasIllegalCharacterReferences(String string) { * * @param value * the codepoint - * @return {@code true} if the codepoint is a valid charater per XML 1.0 definition, {@code false} otherwise. - * @see http://www.w3.org/TR/2008/REC-xml-20081126/#NT-Char + * @return {@code true} if the codepoint is a valid character per XML 1.0 definition, {@code false} otherwise. + * + * @see Definition of a characters range */ public static boolean isLegalXmlCharacter(int value) { return value == 0x9 || value == 0xA || value == 0xD || (value >= 0x20 && value <= 0xD7FF) diff --git a/xmppserver/src/main/webapp/group-create.jsp b/xmppserver/src/main/webapp/group-create.jsp index 3ee8159fa6..4133a92d60 100644 --- a/xmppserver/src/main/webapp/group-create.jsp +++ b/xmppserver/src/main/webapp/group-create.jsp @@ -132,83 +132,75 @@ } } } + + pageContext.setAttribute("groupName", groupName); + if (groupName != null) { + pageContext.setAttribute("group", webManager.getGroupManager().getGroup(groupName)); + } + pageContext.setAttribute( "errors", errors ); + pageContext.setAttribute( "name", name ); + pageContext.setAttribute( "description", description ); %> -<% - // If editing the group. - if (groupName != null) { - %> - <fmt:message key="group.edit.title" /> - <% } - // Otherwise creating a new group. - else { - %> - <fmt:message key="group.create.title" /> - <% } %> - - -<% if (groupName == null) { %> - -<% } - else { %> - -"/> -<% } %> - - + + + <fmt:message key="group.edit.title" /> + + + + <fmt:message key="group.create.title" /> + + + + + -<% if (errors.get("general") != null) { %> - - - -<% } %> + + + + + -<% if (webManager.getGroupManager().isReadOnly()) { %> -
- -
-<% } %> + +
+ +
+

- <% - // If editing the group. - if (groupName != null) { - %> - - <% } - // Otherwise creating a new group. - else { - %> - - <% } %> + + + + + + + +

- <% if (groupName != null) { %> - - <% } %> + + " id="existingName"> +
- <% - // If editing the group. - if (groupName != null) { - %> - - <% } - // Otherwise creating a new group. - else { - %> - - <% } %> + + + + + + + +
@@ -217,63 +209,56 @@ * - <% if (errors.get("name") != null || errors.get("groupAlreadyExists") != null) { %> - + - - <% } %> + + + + + + + - <% if (errors.get("description") != null) { %> - + - + - - <% } %> + @@ -288,19 +273,17 @@ document.f.name.focus(); -<% // Disable the form if a read-only user provider. -if (webManager.getGroupManager().isReadOnly()) { %> - - -<% } %> + + + -%> + diff --git a/xmppserver/src/main/webapp/group-delete.jsp b/xmppserver/src/main/webapp/group-delete.jsp index cccda987d3..352af5fb00 100644 --- a/xmppserver/src/main/webapp/group-delete.jsp +++ b/xmppserver/src/main/webapp/group-delete.jsp @@ -23,6 +23,7 @@ %> <%@ page import="org.jivesoftware.openfire.security.SecurityAuditManager" %> +<%@ taglib uri="admin" prefix="admin" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> @@ -66,13 +67,14 @@ response.sendRedirect("group-summary.jsp?deletesuccess=true"); return; } + pageContext.setAttribute( "group", group ); %> <fmt:message key="group.delete.title"/> - "/> + @@ -85,13 +87,13 @@

-"><%= group.getName() %> +

- +"> "> "> diff --git a/xmppserver/src/main/webapp/group-edit.jsp b/xmppserver/src/main/webapp/group-edit.jsp index ffa2069cba..4335080d45 100644 --- a/xmppserver/src/main/webapp/group-edit.jsp +++ b/xmppserver/src/main/webapp/group-edit.jsp @@ -345,7 +345,7 @@ <fmt:message key="group.edit.title"/> - + @@ -690,7 +690,7 @@ @@ -156,13 +158,13 @@
- " id="gname"> + " id="gname">
  - <% if (errors.get("name") != null) { %> - - <% } else if (errors.get("groupAlreadyExists") != null) { %> - - <% } %> +
  + +
- +
-   -  
- <% - // If editing the group. - if (groupName != null) { - %> - "> - <% } - // Otherwise creating a new group. - else { - %> - "> - <% } %> + + + "> + + + "> + + ">
- + diff --git a/xmppserver/src/main/webapp/group-summary.jsp b/xmppserver/src/main/webapp/group-summary.jsp index facb6a72ce..6304b4ef5b 100644 --- a/xmppserver/src/main/webapp/group-summary.jsp +++ b/xmppserver/src/main/webapp/group-summary.jsp @@ -26,8 +26,10 @@ <%@ page import="org.jivesoftware.util.ListPager" %> <%@ page import="org.jivesoftware.util.ParamUtils" %> -<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> +<%@ taglib uri="admin" prefix="admin" %> <% webManager.init(request, response, session, application, out ); %> @@ -148,7 +150,7 @@ - " + - " + <fmt:message key="global.click_edit"/> - " + <fmt:message key="global.click_delete" />