diff --git a/tomcat-9.0-CVE-2021-30640.patch b/tomcat-9.0-CVE-2021-30640.patch
new file mode 100644
index 0000000..fd19e88
--- /dev/null
+++ b/tomcat-9.0-CVE-2021-30640.patch
@@ -0,0 +1,2666 @@
+Index: apache-tomcat-9.0.43-src/build.properties.default
+===================================================================
+--- apache-tomcat-9.0.43-src.orig/build.properties.default
++++ apache-tomcat-9.0.43-src/build.properties.default
+@@ -244,6 +244,15 @@ objenesis.home=${base.path}/objenesis-${
+ objenesis.jar=${objenesis.home}/objenesis-${objenesis.version}.jar
+ objenesis.loc=${base-maven.loc}/org/objenesis/objenesis/${objenesis.version}/objenesis-${objenesis.version}.jar
+
++# ----- UnboundID, used by unit tests, version 5.1.4 or later -----
++unboundid.version=5.1.4
++unboundid.checksum.enabled=true
++unboundid.checksum.algorithm=SHA-512
++unboundid.checksum.value=04cf7f59eddebdd5b51e5be55021f9d9c667cca6101eac954e7a8d5b51f4c23372cd8f041640157f082435a166b75d85e79252b516130ede7d966dae6d3eae67
++unboundid.home=${base.path}/unboundid-${unboundid.version}
++unboundid.jar=${unboundid.home}/unboundid-ldapsdk-${unboundid.version}.jar
++unboundid.loc=${base-maven.loc}/com/unboundid/unboundid-ldapsdk/${unboundid.version}/unboundid-ldapsdk-${unboundid.version}.jar
++
+ # ----- Checkstyle, version 6.16 or later -----
+ checkstyle.version=8.22
+ checkstyle.checksum.enabled=true
+Index: apache-tomcat-9.0.43-src/build.xml
+===================================================================
+--- apache-tomcat-9.0.43-src.orig/build.xml
++++ apache-tomcat-9.0.43-src/build.xml
+@@ -3164,6 +3164,15 @@ skip.installer property in build.propert
+
+
+
++
++
++
++
++
++
++
++
++
+
+
+ connectionPool = null;
+
+-
+ /**
+ * The pool size limit. If 1, pooling is not used.
+ */
+ protected int connectionPoolSize = 1;
+
+-
+ /**
+ * Whether to use context ClassLoader or default ClassLoader.
+ * True means use context ClassLoader, and True is the default
+@@ -504,37 +476,35 @@ public class JNDIRealm extends RealmBase
+ return forceDnHexEscape;
+ }
+
++
+ public void setForceDnHexEscape(boolean forceDnHexEscape) {
+ this.forceDnHexEscape = forceDnHexEscape;
+ }
+
++
+ /**
+ * @return the type of authentication to use.
+ */
+ public String getAuthentication() {
+-
+ return authentication;
+-
+ }
+
++
+ /**
+ * Set the type of authentication to use.
+ *
+ * @param authentication The authentication
+ */
+ public void setAuthentication(String authentication) {
+-
+ this.authentication = authentication;
+-
+ }
+
++
+ /**
+ * @return the connection username for this Realm.
+ */
+ public String getConnectionName() {
+-
+ return this.connectionName;
+-
+ }
+
+
+@@ -544,9 +514,7 @@ public class JNDIRealm extends RealmBase
+ * @param connectionName The new connection username
+ */
+ public void setConnectionName(String connectionName) {
+-
+ this.connectionName = connectionName;
+-
+ }
+
+
+@@ -554,9 +522,7 @@ public class JNDIRealm extends RealmBase
+ * @return the connection password for this Realm.
+ */
+ public String getConnectionPassword() {
+-
+ return this.connectionPassword;
+-
+ }
+
+
+@@ -566,9 +532,7 @@ public class JNDIRealm extends RealmBase
+ * @param connectionPassword The new connection password
+ */
+ public void setConnectionPassword(String connectionPassword) {
+-
+ this.connectionPassword = connectionPassword;
+-
+ }
+
+
+@@ -576,9 +540,7 @@ public class JNDIRealm extends RealmBase
+ * @return the connection URL for this Realm.
+ */
+ public String getConnectionURL() {
+-
+ return this.connectionURL;
+-
+ }
+
+
+@@ -588,9 +550,7 @@ public class JNDIRealm extends RealmBase
+ * @param connectionURL The new connection URL
+ */
+ public void setConnectionURL(String connectionURL) {
+-
+ this.connectionURL = connectionURL;
+-
+ }
+
+
+@@ -598,9 +558,7 @@ public class JNDIRealm extends RealmBase
+ * @return the JNDI context factory for this Realm.
+ */
+ public String getContextFactory() {
+-
+ return this.contextFactory;
+-
+ }
+
+
+@@ -610,11 +568,10 @@ public class JNDIRealm extends RealmBase
+ * @param contextFactory The new context factory
+ */
+ public void setContextFactory(String contextFactory) {
+-
+ this.contextFactory = contextFactory;
+-
+ }
+
++
+ /**
+ * @return the derefAliases setting to be used.
+ */
+@@ -622,33 +579,32 @@ public class JNDIRealm extends RealmBase
+ return derefAliases;
+ }
+
++
+ /**
+ * Set the value for derefAliases to be used when searching the directory.
+ *
+ * @param derefAliases New value of property derefAliases.
+ */
+ public void setDerefAliases(java.lang.String derefAliases) {
+- this.derefAliases = derefAliases;
++ this.derefAliases = derefAliases;
+ }
+
++
+ /**
+ * @return the protocol to be used.
+ */
+ public String getProtocol() {
+-
+ return protocol;
+-
+ }
+
++
+ /**
+ * Set the protocol for this Realm.
+ *
+ * @param protocol The new protocol.
+ */
+ public void setProtocol(String protocol) {
+-
+ this.protocol = protocol;
+-
+ }
+
+
+@@ -692,9 +648,7 @@ public class JNDIRealm extends RealmBase
+ * @return the base element for user searches.
+ */
+ public String getUserBase() {
+-
+ return this.userBase;
+-
+ }
+
+
+@@ -704,9 +658,7 @@ public class JNDIRealm extends RealmBase
+ * @param userBase The new base element
+ */
+ public void setUserBase(String userBase) {
+-
+ this.userBase = userBase;
+-
+ }
+
+
+@@ -714,9 +666,7 @@ public class JNDIRealm extends RealmBase
+ * @return the message format pattern for selecting users in this Realm.
+ */
+ public String getUserSearch() {
+-
+ return this.userSearch;
+-
+ }
+
+
+@@ -745,9 +695,7 @@ public class JNDIRealm extends RealmBase
+ * @return the "search subtree for users" flag.
+ */
+ public boolean getUserSubtree() {
+-
+ return this.userSubtree;
+-
+ }
+
+
+@@ -757,9 +705,7 @@ public class JNDIRealm extends RealmBase
+ * @param userSubtree The new search flag
+ */
+ public void setUserSubtree(boolean userSubtree) {
+-
+ this.userSubtree = userSubtree;
+-
+ }
+
+
+@@ -767,7 +713,6 @@ public class JNDIRealm extends RealmBase
+ * @return the user role name attribute name for this Realm.
+ */
+ public String getUserRoleName() {
+-
+ return userRoleName;
+ }
+
+@@ -778,9 +723,7 @@ public class JNDIRealm extends RealmBase
+ * @param userRoleName The new userRole name attribute name
+ */
+ public void setUserRoleName(String userRoleName) {
+-
+ this.userRoleName = userRoleName;
+-
+ }
+
+
+@@ -788,9 +731,7 @@ public class JNDIRealm extends RealmBase
+ * @return the base element for role searches.
+ */
+ public String getRoleBase() {
+-
+ return this.roleBase;
+-
+ }
+
+
+@@ -809,9 +750,7 @@ public class JNDIRealm extends RealmBase
+ * @return the role name attribute name for this Realm.
+ */
+ public String getRoleName() {
+-
+ return this.roleName;
+-
+ }
+
+
+@@ -821,9 +760,7 @@ public class JNDIRealm extends RealmBase
+ * @param roleName The new role name attribute name
+ */
+ public void setRoleName(String roleName) {
+-
+ this.roleName = roleName;
+-
+ }
+
+
+@@ -831,9 +768,7 @@ public class JNDIRealm extends RealmBase
+ * @return the message format pattern for selecting roles in this Realm.
+ */
+ public String getRoleSearch() {
+-
+ return this.roleSearch;
+-
+ }
+
+
+@@ -862,9 +797,7 @@ public class JNDIRealm extends RealmBase
+ * @return the "search subtree for roles" flag.
+ */
+ public boolean getRoleSubtree() {
+-
+ return this.roleSubtree;
+-
+ }
+
+
+@@ -874,18 +807,15 @@ public class JNDIRealm extends RealmBase
+ * @param roleSubtree The new search flag
+ */
+ public void setRoleSubtree(boolean roleSubtree) {
+-
+ this.roleSubtree = roleSubtree;
+-
+ }
+
++
+ /**
+ * @return the "The nested group search flag" flag.
+ */
+ public boolean getRoleNested() {
+-
+ return this.roleNested;
+-
+ }
+
+
+@@ -895,9 +825,7 @@ public class JNDIRealm extends RealmBase
+ * @param roleNested The nested group search flag
+ */
+ public void setRoleNested(boolean roleNested) {
+-
+ this.roleNested = roleNested;
+-
+ }
+
+
+@@ -905,9 +833,7 @@ public class JNDIRealm extends RealmBase
+ * @return the password attribute used to retrieve the user password.
+ */
+ public String getUserPassword() {
+-
+ return this.userPassword;
+-
+ }
+
+
+@@ -917,9 +843,7 @@ public class JNDIRealm extends RealmBase
+ * @param userPassword The new password attribute
+ */
+ public void setUserPassword(String userPassword) {
+-
+ this.userPassword = userPassword;
+-
+ }
+
+
+@@ -927,6 +851,7 @@ public class JNDIRealm extends RealmBase
+ return userRoleAttribute;
+ }
+
++
+ public void setUserRoleAttribute(String userRoleAttribute) {
+ this.userRoleAttribute = userRoleAttribute;
+ }
+@@ -935,14 +860,10 @@ public class JNDIRealm extends RealmBase
+ * @return the message format pattern for selecting users in this Realm.
+ */
+ public String getUserPattern() {
+-
+ return this.userPattern;
+-
+ }
+
+
+-
+-
+ /**
+ * Set the message format pattern for selecting users in this Realm.
+ * This may be one simple pattern, or multiple patterns to be tried,
+@@ -970,9 +891,7 @@ public class JNDIRealm extends RealmBase
+ * @return Value of property alternateURL.
+ */
+ public String getAlternateURL() {
+-
+ return this.alternateURL;
+-
+ }
+
+
+@@ -982,9 +901,7 @@ public class JNDIRealm extends RealmBase
+ * @param alternateURL New value of property alternateURL.
+ */
+ public void setAlternateURL(String alternateURL) {
+-
+ this.alternateURL = alternateURL;
+-
+ }
+
+
+@@ -992,9 +909,7 @@ public class JNDIRealm extends RealmBase
+ * @return the common role
+ */
+ public String getCommonRole() {
+-
+ return commonRole;
+-
+ }
+
+
+@@ -1004,9 +919,7 @@ public class JNDIRealm extends RealmBase
+ * @param commonRole The common role
+ */
+ public void setCommonRole(String commonRole) {
+-
+ this.commonRole = commonRole;
+-
+ }
+
+
+@@ -1014,9 +927,7 @@ public class JNDIRealm extends RealmBase
+ * @return the connection timeout.
+ */
+ public String getConnectionTimeout() {
+-
+ return connectionTimeout;
+-
+ }
+
+
+@@ -1026,18 +937,15 @@ public class JNDIRealm extends RealmBase
+ * @param timeout The new connection timeout
+ */
+ public void setConnectionTimeout(String timeout) {
+-
+ this.connectionTimeout = timeout;
+-
+ }
+
++
+ /**
+ * @return the read timeout.
+ */
+ public String getReadTimeout() {
+-
+ return readTimeout;
+-
+ }
+
+
+@@ -1047,9 +955,7 @@ public class JNDIRealm extends RealmBase
+ * @param timeout The new read timeout
+ */
+ public void setReadTimeout(String timeout) {
+-
+ this.readTimeout = timeout;
+-
+ }
+
+
+@@ -1077,6 +983,7 @@ public class JNDIRealm extends RealmBase
+ return useDelegatedCredential;
+ }
+
++
+ public void setUseDelegatedCredential(boolean useDelegatedCredential) {
+ this.useDelegatedCredential = useDelegatedCredential;
+ }
+@@ -1086,6 +993,7 @@ public class JNDIRealm extends RealmBase
+ return spnegoDelegationQop;
+ }
+
++
+ public void setSpnegoDelegationQop(String spnegoDelegationQop) {
+ this.spnegoDelegationQop = spnegoDelegationQop;
+ }
+@@ -1098,6 +1006,7 @@ public class JNDIRealm extends RealmBase
+ return useStartTls;
+ }
+
++
+ /**
+ * Flag whether StartTLS should be used when connecting to the ldap server
+ *
+@@ -1109,6 +1018,7 @@ public class JNDIRealm extends RealmBase
+ this.useStartTls = useStartTls;
+ }
+
++
+ /**
+ * @return list of the allowed cipher suites when connections are made using
+ * StartTLS
+@@ -1128,6 +1038,7 @@ public class JNDIRealm extends RealmBase
+ return this.cipherSuitesArray;
+ }
+
++
+ /**
+ * Set the allowed cipher suites when opening a connection using StartTLS.
+ * The cipher suites are expected as a comma separated list.
+@@ -1139,6 +1050,7 @@ public class JNDIRealm extends RealmBase
+ this.cipherSuites = suites;
+ }
+
++
+ /**
+ * @return the connection pool size, or the default value 1 if pooling
+ * is disabled
+@@ -1147,6 +1059,7 @@ public class JNDIRealm extends RealmBase
+ return connectionPoolSize;
+ }
+
++
+ /**
+ * Set the connection pool size
+ * @param connectionPoolSize the new pool size
+@@ -1155,6 +1068,7 @@ public class JNDIRealm extends RealmBase
+ this.connectionPoolSize = connectionPoolSize;
+ }
+
++
+ /**
+ * @return name of the {@link HostnameVerifier} class used for connections
+ * using StartTLS, or the empty string, if the default verifier
+@@ -1167,6 +1081,7 @@ public class JNDIRealm extends RealmBase
+ return this.hostnameVerifier.getClass().getCanonicalName();
+ }
+
++
+ /**
+ * Set the {@link HostnameVerifier} to be used when opening connections
+ * using StartTLS. An instance of the given class name will be constructed
+@@ -1183,6 +1098,7 @@ public class JNDIRealm extends RealmBase
+ }
+ }
+
++
+ /**
+ * @return the {@link HostnameVerifier} to use for peer certificate
+ * verification when opening connections using StartTLS.
+@@ -1191,8 +1107,7 @@ public class JNDIRealm extends RealmBase
+ if (this.hostnameVerifier != null) {
+ return this.hostnameVerifier;
+ }
+- if (this.hostNameVerifierClassName == null
+- || hostNameVerifierClassName.equals("")) {
++ if (this.hostNameVerifierClassName == null || hostNameVerifierClassName.equals("")) {
+ return null;
+ }
+ try {
+@@ -1212,6 +1127,7 @@ public class JNDIRealm extends RealmBase
+ }
+ }
+
++
+ /**
+ * Set the {@link SSLSocketFactory} to be used when opening connections
+ * using StartTLS. An instance of the factory with the given name will be
+@@ -1225,6 +1141,7 @@ public class JNDIRealm extends RealmBase
+ this.sslSocketFactoryClassName = factoryClassName;
+ }
+
++
+ /**
+ * Set the ssl protocol to be used for connections using StartTLS.
+ *
+@@ -1235,6 +1152,7 @@ public class JNDIRealm extends RealmBase
+ this.sslProtocol = protocol;
+ }
+
++
+ /**
+ * @return the list of supported ssl protocols by the default
+ * {@link SSLContext}
+@@ -1248,12 +1166,14 @@ public class JNDIRealm extends RealmBase
+ }
+ }
+
++
+ private Object constructInstance(String className)
+ throws ReflectiveOperationException {
+ Class> clazz = Class.forName(className);
+ return clazz.getConstructor().newInstance();
+ }
+
++
+ /**
+ * Sets whether to use the context or default ClassLoader.
+ * True means use context ClassLoader.
+@@ -1264,6 +1184,7 @@ public class JNDIRealm extends RealmBase
+ useContextClassLoader = useContext;
+ }
+
++
+ /**
+ * Returns whether to use the context or default ClassLoader.
+ * True means to use the context ClassLoader.
+@@ -1274,6 +1195,7 @@ public class JNDIRealm extends RealmBase
+ return useContextClassLoader;
+ }
+
++
+ // ---------------------------------------------------------- Realm Methods
+
+ /**
+@@ -1333,7 +1255,7 @@ public class JNDIRealm extends RealmBase
+ closePooledConnections();
+
+ // open a new directory context.
+- connection = get();
++ connection = get(true);
+
+ // Try the authentication again.
+ principal = authenticate(connection, username, credentials);
+@@ -1356,21 +1278,14 @@ public class JNDIRealm extends RealmBase
+ closePooledConnections();
+
+ // Return "not authenticated" for this request
+- if (containerLog.isDebugEnabled())
++ if (containerLog.isDebugEnabled()) {
+ containerLog.debug("Returning null principal.");
++ }
+ return null;
+-
+ }
+-
+ }
+
+
+- // -------------------------------------------------------- Package Methods
+-
+-
+- // ------------------------------------------------------ Protected Methods
+-
+-
+ /**
+ * Return the Principal associated with the specified username and
+ * credentials, if there is one; otherwise return null
.
+@@ -1383,22 +1298,18 @@ public class JNDIRealm extends RealmBase
+ *
+ * @exception NamingException if a directory server error occurs
+ */
+- public Principal authenticate(JNDIConnection connection,
+- String username,
+- String credentials)
+- throws NamingException {
+-
+- if (username == null || username.equals("")
+- || credentials == null || credentials.equals("")) {
+- if (containerLog.isDebugEnabled())
++ public Principal authenticate(JNDIConnection connection, String username, String credentials)
++ throws NamingException {
++
++ if (username == null || username.equals("") || credentials == null || credentials.equals("")) {
++ if (containerLog.isDebugEnabled()) {
+ containerLog.debug("username null or empty: returning null principal.");
++ }
+ return null;
+ }
+
+ if (userPatternArray != null) {
+- for (int curUserPattern = 0;
+- curUserPattern < userPatternArray.length;
+- curUserPattern++) {
++ for (int curUserPattern = 0; curUserPattern < userPatternArray.length; curUserPattern++) {
+ // Retrieve user information
+ User user = getUser(connection, username, credentials, curUserPattern);
+ if (user != null) {
+@@ -1426,12 +1337,14 @@ public class JNDIRealm extends RealmBase
+ } else {
+ // Retrieve user information
+ User user = getUser(connection, username, credentials);
+- if (user == null)
++ if (user == null) {
+ return null;
++ }
+
+ // Check the user's credentials
+- if (!checkCredentials(connection.context, user, credentials))
++ if (!checkCredentials(connection.context, user, credentials)) {
+ return null;
++ }
+
+ // Search for additional roles
+ List roles = getRoles(connection, user);
+@@ -1445,6 +1358,8 @@ public class JNDIRealm extends RealmBase
+ }
+
+
++ // ------------------------------------------------------ Protected Methods
++
+ /**
+ * Return a User object containing information about the user
+ * with the specified username, if found in the directory;
+@@ -1457,9 +1372,7 @@ public class JNDIRealm extends RealmBase
+ *
+ * @see #getUser(JNDIConnection, String, String, int)
+ */
+- protected User getUser(JNDIConnection connection, String username)
+- throws NamingException {
+-
++ protected User getUser(JNDIConnection connection, String username) throws NamingException {
+ return getUser(connection, username, null, -1);
+ }
+
+@@ -1477,9 +1390,7 @@ public class JNDIRealm extends RealmBase
+ *
+ * @see #getUser(JNDIConnection, String, String, int)
+ */
+- protected User getUser(JNDIConnection connection, String username, String credentials)
+- throws NamingException {
+-
++ protected User getUser(JNDIConnection connection, String username, String credentials) throws NamingException {
+ return getUser(connection, username, credentials, -1);
+ }
+
+@@ -1502,18 +1413,19 @@ public class JNDIRealm extends RealmBase
+ * @return the User object
+ * @exception NamingException if a directory server error occurs
+ */
+- protected User getUser(JNDIConnection connection, String username,
+- String credentials, int curUserPattern)
+- throws NamingException {
++ protected User getUser(JNDIConnection connection, String username, String credentials, int curUserPattern)
++ throws NamingException {
+
+ User user = null;
+
+ // Get attributes to retrieve from user entry
+ List list = new ArrayList<>();
+- if (userPassword != null)
++ if (userPassword != null) {
+ list.add(userPassword);
+- if (userRoleName != null)
++ }
++ if (userRoleName != null) {
+ list.add(userRoleName);
++ }
+ if (userRoleAttribute != null) {
+ list.add(userRoleAttribute);
+ }
+@@ -1545,8 +1457,7 @@ public class JNDIRealm extends RealmBase
+ if (userPassword == null && credentials != null && user != null) {
+ // The password is available. Insert it since it may be required for
+ // role searches.
+- return new User(user.getUserName(), user.getDN(), credentials,
+- user.getRoles(), user.getUserRoleId());
++ return new User(user.getUserName(), user.getDN(), credentials, user.getRoles(), user.getUserRoleId());
+ }
+
+ return user;
+@@ -1566,11 +1477,8 @@ public class JNDIRealm extends RealmBase
+ * @return the User object
+ * @exception NamingException if a directory server error occurs
+ */
+- protected User getUserByPattern(DirContext context,
+- String username,
+- String[] attrIds,
+- String dn)
+- throws NamingException {
++ protected User getUserByPattern(DirContext context, String username, String[] attrIds, String dn)
++ throws NamingException {
+
+ // If no attributes are requested, no need to look for them
+ if (attrIds == null || attrIds.length == 0) {
+@@ -1584,13 +1492,15 @@ public class JNDIRealm extends RealmBase
+ } catch (NameNotFoundException e) {
+ return null;
+ }
+- if (attrs == null)
++ if (attrs == null) {
+ return null;
++ }
+
+ // Retrieve value of userPassword
+ String password = null;
+- if (userPassword != null)
++ if (userPassword != null) {
+ password = getAttributeValue(userPassword, attrs);
++ }
+
+ String userRoleAttrValue = null;
+ if (userRoleAttribute != null) {
+@@ -1599,8 +1509,9 @@ public class JNDIRealm extends RealmBase
+
+ // Retrieve values of userRoleName attribute
+ ArrayList roles = null;
+- if (userRoleName != null)
++ if (userRoleName != null) {
+ roles = addAttributeValues(userRoleName, attrs, roles);
++ }
+
+ return new User(username, dn, password, roles, userRoleAttrValue);
+ }
+@@ -1621,20 +1532,20 @@ public class JNDIRealm extends RealmBase
+ * @exception NamingException if a directory server error occurs
+ * @see #getUserByPattern(DirContext, String, String[], String)
+ */
+- protected User getUserByPattern(JNDIConnection connection,
+- String username,
+- String credentials,
+- String[] attrIds,
+- int curUserPattern)
+- throws NamingException {
++ protected User getUserByPattern(JNDIConnection connection, String username, String credentials, String[] attrIds,
++ int curUserPattern) throws NamingException {
+
+ User user = null;
+
+- if (username == null || userPatternArray[curUserPattern] == null)
++ if (username == null || userPatternArray[curUserPattern] == null) {
+ return null;
++ }
+
+- // Form the dn from the user pattern
+- String dn = connection.userPatternFormatArray[curUserPattern].format(new String[] { username });
++ // Form the DistinguishedName from the user pattern.
++ // Escape in case username contains a character with special meaning in
++ // an attribute value.
++ String dn = connection.userPatternFormatArray[curUserPattern].format(
++ new String[] { doAttributeValueEscaping(username) });
+
+ try {
+ user = getUserByPattern(connection.context, username, attrIds, dn);
+@@ -1666,16 +1577,17 @@ public class JNDIRealm extends RealmBase
+ * @return the User object
+ * @exception NamingException if a directory server error occurs
+ */
+- protected User getUserBySearch(JNDIConnection connection,
+- String username,
+- String[] attrIds)
+- throws NamingException {
++ protected User getUserBySearch(JNDIConnection connection, String username, String[] attrIds)
++ throws NamingException {
+
+- if (username == null || connection.userSearchFormat == null)
++ if (username == null || connection.userSearchFormat == null) {
+ return null;
++ }
+
+ // Form the search filter
+- String filter = connection.userSearchFormat.format(new String[] { username });
++ // Escape in case username contains a character with special meaning in
++ // a search filter.
++ String filter = connection.userSearchFormat.format(new String[] { doFilterEscaping(username) });
+
+ // Set up the search controls
+ SearchControls constraints = new SearchControls();
+@@ -1690,12 +1602,12 @@ public class JNDIRealm extends RealmBase
+ constraints.setTimeLimit(timeLimit);
+
+ // Specify the attributes to be retrieved
+- if (attrIds == null)
++ if (attrIds == null) {
+ attrIds = new String[0];
++ }
+ constraints.setReturningAttributes(attrIds);
+
+- NamingEnumeration results =
+- connection.context.search(userBase, filter, constraints);
++ NamingEnumeration results = connection.context.search(userBase, filter, constraints);
+
+ try {
+ // Fail if no entries found
+@@ -1704,10 +1616,11 @@ public class JNDIRealm extends RealmBase
+ return null;
+ }
+ } catch (PartialResultException ex) {
+- if (!adCompat)
++ if (!adCompat) {
+ throw ex;
+- else
++ } else {
+ return null;
++ }
+ }
+
+ // Get result for the first entry found
+@@ -1722,24 +1635,28 @@ public class JNDIRealm extends RealmBase
+ return null;
+ }
+ } catch (PartialResultException ex) {
+- if (!adCompat)
++ if (!adCompat) {
+ throw ex;
++ }
+ }
+
+ String dn = getDistinguishedName(connection.context, userBase, result);
+
+- if (containerLog.isTraceEnabled())
++ if (containerLog.isTraceEnabled()) {
+ containerLog.trace(" entry found for " + username + " with dn " + dn);
++ }
+
+ // Get the entry's attributes
+ Attributes attrs = result.getAttributes();
+- if (attrs == null)
++ if (attrs == null) {
+ return null;
++ }
+
+ // Retrieve value of userPassword
+ String password = null;
+- if (userPassword != null)
++ if (userPassword != null) {
+ password = getAttributeValue(userPassword, attrs);
++ }
+
+ String userRoleAttrValue = null;
+ if (userRoleAttribute != null) {
+@@ -1748,8 +1665,9 @@ public class JNDIRealm extends RealmBase
+
+ // Retrieve values of userRoleName attribute
+ ArrayList roles = null;
+- if (userRoleName != null)
++ if (userRoleName != null) {
+ roles = addAttributeValues(userRoleName, attrs, roles);
++ }
+
+ return new User(username, dn, password, roles, userRoleAttrValue);
+ } finally {
+@@ -1775,30 +1693,25 @@ public class JNDIRealm extends RealmBase
+ * @return true
if the credentials are validated
+ * @exception NamingException if a directory server error occurs
+ */
+- protected boolean checkCredentials(DirContext context,
+- User user,
+- String credentials)
+- throws NamingException {
+-
+- boolean validated = false;
+-
+- if (userPassword == null) {
+- validated = bindAsUser(context, user, credentials);
+- } else {
+- validated = compareCredentials(context, user, credentials);
+- }
+-
+- if (containerLog.isTraceEnabled()) {
+- if (validated) {
+- containerLog.trace(sm.getString("jndiRealm.authenticateSuccess",
+- user.getUserName()));
+- } else {
+- containerLog.trace(sm.getString("jndiRealm.authenticateFailure",
+- user.getUserName()));
+- }
+- }
+- return validated;
+- }
++ protected boolean checkCredentials(DirContext context, User user, String credentials) throws NamingException {
++
++ boolean validated = false;
++
++ if (userPassword == null) {
++ validated = bindAsUser(context, user, credentials);
++ } else {
++ validated = compareCredentials(context, user, credentials);
++ }
++
++ if (containerLog.isTraceEnabled()) {
++ if (validated) {
++ containerLog.trace(sm.getString("jndiRealm.authenticateSuccess", user.getUserName()));
++ } else {
++ containerLog.trace(sm.getString("jndiRealm.authenticateFailure", user.getUserName()));
++ }
++ }
++ return validated;
++ }
+
+
+ /**
+@@ -1811,17 +1724,15 @@ public class JNDIRealm extends RealmBase
+ * @return true
if the credentials are validated
+ * @exception NamingException if a directory server error occurs
+ */
+- protected boolean compareCredentials(DirContext context,
+- User info,
+- String credentials)
+- throws NamingException {
+-
++ protected boolean compareCredentials(DirContext context, User info, String credentials) throws NamingException {
+ // Validate the credentials specified by the user
+- if (containerLog.isTraceEnabled())
++ if (containerLog.isTraceEnabled()) {
+ containerLog.trace(" validating credentials");
++ }
+
+- if (info == null || credentials == null)
++ if (info == null || credentials == null) {
+ return false;
++ }
+
+ String password = info.getPassword();
+
+@@ -1838,21 +1749,22 @@ public class JNDIRealm extends RealmBase
+ * @return true
if the credentials are validated
+ * @exception NamingException if a directory server error occurs
+ */
+- protected boolean bindAsUser(DirContext context,
+- User user,
+- String credentials)
+- throws NamingException {
+-
+- if (credentials == null || user == null)
+- return false;
+-
+- String dn = user.getDN();
+- if (dn == null)
+- return false;
+-
+- // Validate the credentials specified by the user
+- if (containerLog.isTraceEnabled()) {
+- containerLog.trace(" validating credentials by binding as the user");
++ protected boolean bindAsUser(DirContext context, User user, String credentials) throws NamingException {
++
++ if (credentials == null || user == null) {
++ return false;
++ }
++
++ // This is returned from the directory so will be attribute value
++ // escaped if required
++ String dn = user.getDN();
++ if (dn == null) {
++ return false;
++ }
++
++ // Validate the credentials specified by the user
++ if (containerLog.isTraceEnabled()) {
++ containerLog.trace(" validating credentials by binding as the user");
+ }
+
+ userCredentialsAdd(context, dn, credentials);
+@@ -1877,48 +1789,47 @@ public class JNDIRealm extends RealmBase
+ return validated;
+ }
+
+- /**
+- * Configure the context to use the provided credentials for
+- * authentication.
+- *
+- * @param context DirContext to configure
+- * @param dn Distinguished name of user
+- * @param credentials Credentials of user
+- * @exception NamingException if a directory server error occurs
+- */
+- private void userCredentialsAdd(DirContext context, String dn,
+- String credentials) throws NamingException {
++
++ /**
++ * Configure the context to use the provided credentials for
++ * authentication.
++ *
++ * @param context DirContext to configure
++ * @param dn Distinguished name of user
++ * @param credentials Credentials of user
++ * @exception NamingException if a directory server error occurs
++ */
++ private void userCredentialsAdd(DirContext context, String dn, String credentials) throws NamingException {
+ // Set up security environment to bind as the user
+ context.addToEnvironment(Context.SECURITY_PRINCIPAL, dn);
+ context.addToEnvironment(Context.SECURITY_CREDENTIALS, credentials);
+ }
+
++
+ /**
+ * Configure the context to use {@link #connectionName} and
+ * {@link #connectionPassword} if specified or an anonymous connection if
+ * those attributes are not specified.
+ *
+- * @param context DirContext to configure
+- * @exception NamingException if a directory server error occurs
++ * @param context DirContext to configure
++ * @exception NamingException if a directory server error occurs
+ */
+- private void userCredentialsRemove(DirContext context)
+- throws NamingException {
++ private void userCredentialsRemove(DirContext context) throws NamingException {
+ // Restore the original security environment
+ if (connectionName != null) {
+- context.addToEnvironment(Context.SECURITY_PRINCIPAL,
+- connectionName);
++ context.addToEnvironment(Context.SECURITY_PRINCIPAL, connectionName);
+ } else {
+ context.removeFromEnvironment(Context.SECURITY_PRINCIPAL);
+ }
+
+ if (connectionPassword != null) {
+- context.addToEnvironment(Context.SECURITY_CREDENTIALS,
+- connectionPassword);
++ context.addToEnvironment(Context.SECURITY_CREDENTIALS, connectionPassword);
+ } else {
+ context.removeFromEnvironment(Context.SECURITY_CREDENTIALS);
+ }
+ }
+
++
+ /**
+ * Return a List of roles associated with the given User. Any
+ * roles present in the user's directory entry are supplemented by
+@@ -1930,21 +1841,27 @@ public class JNDIRealm extends RealmBase
+ * @return the list of role names
+ * @exception NamingException if a directory server error occurs
+ */
+- protected List getRoles(JNDIConnection connection, User user)
+- throws NamingException {
++ protected List getRoles(JNDIConnection connection, User user) throws NamingException {
+
+- if (user == null)
++ if (user == null) {
+ return null;
++ }
+
++ // This is returned from the directory so will be attribute value
++ // escaped if required
+ String dn = user.getDN();
++ // This is the name the user provided to the authentication process so
++ // it will not be escaped
+ String username = user.getUserName();
+ String userRoleId = user.getUserRoleId();
+
+- if (dn == null || username == null)
++ if (dn == null || username == null) {
+ return null;
++ }
+
+- if (containerLog.isTraceEnabled())
++ if (containerLog.isTraceEnabled()) {
+ containerLog.trace(" getRoles(" + dn + ")");
++ }
+
+ // Start with roles retrieved from the user entry
+ List list = new ArrayList<>();
+@@ -1952,8 +1869,9 @@ public class JNDIRealm extends RealmBase
+ if (userRoles != null) {
+ list.addAll(userRoles);
+ }
+- if (commonRole != null)
++ if (commonRole != null) {
+ list.add(commonRole);
++ }
+
+ if (containerLog.isTraceEnabled()) {
+ containerLog.trace(" Found " + list.size() + " user internal roles");
+@@ -1961,16 +1879,23 @@ public class JNDIRealm extends RealmBase
+ }
+
+ // Are we configured to do role searches?
+- if ((connection.roleFormat == null) || (roleName == null))
++ if ((connection.roleFormat == null) || (roleName == null)) {
+ return list;
++ }
+
+- // Set up parameters for an appropriate search
+- String filter = connection.roleFormat.format(new String[] { doRFC2254Encoding(dn), username, userRoleId });
++ // Set up parameters for an appropriate search filter
++ // The dn is already attribute value escaped but the others are not
++ // This is a filter so all input will require filter escaping
++ String filter = connection.roleFormat.format(new String[] {
++ doFilterEscaping(dn),
++ doFilterEscaping(doAttributeValueEscaping(username)),
++ doFilterEscaping(doAttributeValueEscaping(userRoleId)) });
+ SearchControls controls = new SearchControls();
+- if (roleSubtree)
++ if (roleSubtree) {
+ controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+- else
++ } else {
+ controls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
++ }
+ controls.setReturningAttributes(new String[] {roleName});
+
+ String base = null;
+@@ -1979,7 +1904,9 @@ public class JNDIRealm extends RealmBase
+ Name name = np.parse(dn);
+ String nameParts[] = new String[name.size()];
+ for (int i = 0; i < name.size(); i++) {
+- nameParts[i] = name.get(i);
++ // May have been returned with \ escaping rather than
++ // \. Make sure it is \.
++ nameParts[i] = convertToHexEscape(name.get(i));
+ }
+ base = connection.roleBaseFormat.format(nameParts);
+ } else {
+@@ -1990,25 +1917,28 @@ public class JNDIRealm extends RealmBase
+ NamingEnumeration results = searchAsUser(connection.context, user, base, filter, controls,
+ isRoleSearchAsUser());
+
+- if (results == null)
++ if (results == null) {
+ return list; // Should never happen, but just in case ...
++ }
+
+ Map groupMap = new HashMap<>();
+ try {
+ while (results.hasMore()) {
+ SearchResult result = results.next();
+ Attributes attrs = result.getAttributes();
+- if (attrs == null)
++ if (attrs == null) {
+ continue;
+- String dname = getDistinguishedName(connection.context, roleBase, result);
++ }
++ String dname = getDistinguishedName(connection.context, base, result);
+ String name = getAttributeValue(roleName, attrs);
+ if (name != null && dname != null) {
+ groupMap.put(dname, name);
+ }
+ }
+ } catch (PartialResultException ex) {
+- if (!adCompat)
++ if (!adCompat) {
+ throw ex;
++ }
+ } finally {
+ results.close();
+ }
+@@ -2033,22 +1963,28 @@ public class JNDIRealm extends RealmBase
+ Map newThisRound = new HashMap<>(); // Stores the groups we find in this iteration
+
+ for (Entry group : newGroups.entrySet()) {
+- filter = connection.roleFormat.format(new String[] { doRFC2254Encoding(group.getKey()),
+- group.getValue(), group.getValue() });
++ // Group key is already value escaped if required
++ // Group value is not value escaped
++ // Everything needs to be filter escaped
++ filter = connection.roleFormat.format(new String[] {
++ doFilterEscaping(group.getKey()),
++ doFilterEscaping(doAttributeValueEscaping(group.getValue())),
++ doFilterEscaping(doAttributeValueEscaping(group.getValue())) });
+
+ if (containerLog.isTraceEnabled()) {
+- containerLog.trace("Perform a nested group search with base "+ roleBase + " and filter " + filter);
++ containerLog.trace("Perform a nested group search with base "+ roleBase +
++ " and filter " + filter);
+ }
+
+- results = searchAsUser(connection.context, user, roleBase, filter, controls,
+- isRoleSearchAsUser());
++ results = searchAsUser(connection.context, user, base, filter, controls, isRoleSearchAsUser());
+
+ try {
+ while (results.hasMore()) {
+ SearchResult result = results.next();
+ Attributes attrs = result.getAttributes();
+- if (attrs == null)
++ if (attrs == null) {
+ continue;
++ }
+ String dname = getDistinguishedName(connection.context, roleBase, result);
+ String name = getAttributeValue(roleName, attrs);
+ if (name != null && dname != null && !groupMap.keySet().contains(dname)) {
+@@ -2058,12 +1994,12 @@ public class JNDIRealm extends RealmBase
+ if (containerLog.isTraceEnabled()) {
+ containerLog.trace(" Found nested role " + dname + " -> " + name);
+ }
+-
+ }
+- }
++ }
+ } catch (PartialResultException ex) {
+- if (!adCompat)
++ if (!adCompat) {
+ throw ex;
++ }
+ } finally {
+ results.close();
+ }
+@@ -2077,6 +2013,7 @@ public class JNDIRealm extends RealmBase
+ return list;
+ }
+
++
+ /**
+ * Perform the search on the context as the {@code dn}, when
+ * {@code searchAsUser} is {@code true}, otherwise search the context with
+@@ -2099,8 +2036,7 @@ public class JNDIRealm extends RealmBase
+ * @throws NamingException
+ * if a directory server error occurs
+ */
+- private NamingEnumeration searchAsUser(DirContext context,
+- User user, String base, String filter,
++ private NamingEnumeration searchAsUser(DirContext context, User user, String base, String filter,
+ SearchControls controls, boolean searchAsUser) throws NamingException {
+ NamingEnumeration results;
+ try {
+@@ -2125,26 +2061,30 @@ public class JNDIRealm extends RealmBase
+ * @return the attribute value
+ * @exception NamingException if a directory server error occurs
+ */
+- private String getAttributeValue(String attrId, Attributes attrs)
+- throws NamingException {
++ private String getAttributeValue(String attrId, Attributes attrs) throws NamingException {
+
+- if (containerLog.isTraceEnabled())
++ if (containerLog.isTraceEnabled()) {
+ containerLog.trace(" retrieving attribute " + attrId);
++ }
+
+- if (attrId == null || attrs == null)
++ if (attrId == null || attrs == null) {
+ return null;
++ }
+
+ Attribute attr = attrs.get(attrId);
+- if (attr == null)
++ if (attr == null) {
+ return null;
++ }
+ Object value = attr.get();
+- if (value == null)
++ if (value == null) {
+ return null;
++ }
+ String valueString = null;
+- if (value instanceof byte[])
++ if (value instanceof byte[]) {
+ valueString = new String((byte[]) value);
+- else
++ } else {
+ valueString = value.toString();
++ }
+
+ return valueString;
+ }
+@@ -2159,20 +2099,22 @@ public class JNDIRealm extends RealmBase
+ * @return the list of attribute values
+ * @exception NamingException if a directory server error occurs
+ */
+- private ArrayList addAttributeValues(String attrId,
+- Attributes attrs,
+- ArrayList values)
+- throws NamingException{
++ private ArrayList addAttributeValues(String attrId, Attributes attrs, ArrayList values)
++ throws NamingException {
+
+- if (containerLog.isTraceEnabled())
++ if (containerLog.isTraceEnabled()) {
+ containerLog.trace(" retrieving values for attribute " + attrId);
+- if (attrId == null || attrs == null)
++ }
++ if (attrId == null || attrs == null) {
+ return values;
+- if (values == null)
++ }
++ if (values == null) {
+ values = new ArrayList<>();
++ }
+ Attribute attr = attrs.get(attrId);
+- if (attr == null)
++ if (attr == null) {
+ return values;
++ }
+ NamingEnumeration> e = attr.getAll();
+ try {
+ while(e.hasMore()) {
+@@ -2180,8 +2122,9 @@ public class JNDIRealm extends RealmBase
+ values.add(value);
+ }
+ } catch (PartialResultException ex) {
+- if (!adCompat)
++ if (!adCompat) {
+ throw ex;
++ }
+ } finally {
+ e.close();
+ }
+@@ -2214,8 +2157,9 @@ public class JNDIRealm extends RealmBase
+ }
+ // Close our opened connection
+ try {
+- if (containerLog.isDebugEnabled())
++ if (containerLog.isDebugEnabled()) {
+ containerLog.debug("Closing directory context");
++ }
+ connection.context.close();
+ } catch (NamingException e) {
+ containerLog.error(sm.getString("jndiRealm.close"), e);
+@@ -2225,9 +2169,9 @@ public class JNDIRealm extends RealmBase
+ if (connectionPool == null) {
+ singleConnectionLock.unlock();
+ }
+-
+ }
+
++
+ /**
+ * Close all pooled connections.
+ */
+@@ -2243,6 +2187,7 @@ public class JNDIRealm extends RealmBase
+ }
+ }
+
++
+ /**
+ * Get the password for the specified user.
+ * @param username The user name
+@@ -2258,7 +2203,6 @@ public class JNDIRealm extends RealmBase
+ JNDIConnection connection = null;
+ User user = null;
+ try {
+-
+ // Ensure that we have a directory context available
+ connection = get();
+
+@@ -2281,7 +2225,6 @@ public class JNDIRealm extends RealmBase
+ user = getUser(connection, username, null);
+ }
+
+-
+ // Release this context
+ release(connection);
+
+@@ -2292,15 +2235,14 @@ public class JNDIRealm extends RealmBase
+ // ... and have a password
+ return user.getPassword();
+ }
+-
+ } catch (NamingException e) {
+ // Log the problem for posterity
+ containerLog.error(sm.getString("jndiRealm.exception"), e);
+ return null;
+ }
+-
+ }
+
++
+ /**
+ * Get the principal associated with the specified certificate.
+ * @param username The user name
+@@ -2311,9 +2253,9 @@ public class JNDIRealm extends RealmBase
+ return getPrincipal(username, null);
+ }
+
++
+ @Override
+- protected Principal getPrincipal(GSSName gssName,
+- GSSCredential gssCredential) {
++ protected Principal getPrincipal(GSSName gssName, GSSCredential gssCredential) {
+ String name = gssName.toString();
+
+ if (isStripRealmForGss()) {
+@@ -2327,15 +2269,14 @@ public class JNDIRealm extends RealmBase
+ return getPrincipal(name, gssCredential);
+ }
+
++
+ @Override
+- protected Principal getPrincipal(String username,
+- GSSCredential gssCredential) {
++ protected Principal getPrincipal(String username, GSSCredential gssCredential) {
+
+ JNDIConnection connection = null;
+ Principal principal = null;
+
+ try {
+-
+ // Ensure that we have a directory context available
+ connection = get();
+
+@@ -2347,7 +2288,6 @@ public class JNDIRealm extends RealmBase
+ principal = getPrincipal(connection, username, gssCredential);
+
+ } catch (CommunicationException | ServiceUnavailableException e) {
+-
+ // log the exception so we know it's there.
+ containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
+
+@@ -2360,10 +2300,8 @@ public class JNDIRealm extends RealmBase
+
+ // Try the authentication again.
+ principal = getPrincipal(connection, username, gssCredential);
+-
+ }
+
+-
+ // Release this context
+ release(connection);
+
+@@ -2371,16 +2309,12 @@ public class JNDIRealm extends RealmBase
+ return principal;
+
+ } catch (NamingException e) {
+-
+ // Log the problem for posterity
+ containerLog.error(sm.getString("jndiRealm.exception"), e);
+
+ // Return "not authenticated" for this request
+ return null;
+-
+ }
+-
+-
+ }
+
+
+@@ -2392,9 +2326,8 @@ public class JNDIRealm extends RealmBase
+ * @return the Principal associated with the given certificate.
+ * @exception NamingException if a directory server error occurs
+ */
+- protected Principal getPrincipal(JNDIConnection connection,
+- String username, GSSCredential gssCredential)
+- throws NamingException {
++ protected Principal getPrincipal(JNDIConnection connection, String username, GSSCredential gssCredential)
++ throws NamingException {
+
+ User user = null;
+ List roles = null;
+@@ -2406,12 +2339,9 @@ public class JNDIRealm extends RealmBase
+ // Preserve the current context environment parameters
+ preservedEnvironment = context.getEnvironment();
+ // Set up context
+- context.addToEnvironment(
+- Context.SECURITY_AUTHENTICATION, "GSSAPI");
+- context.addToEnvironment(
+- "javax.security.sasl.server.authentication", "true");
+- context.addToEnvironment(
+- "javax.security.sasl.qop", spnegoDelegationQop);
++ context.addToEnvironment(Context.SECURITY_AUTHENTICATION, "GSSAPI");
++ context.addToEnvironment("javax.security.sasl.server.authentication", "true");
++ context.addToEnvironment("javax.security.sasl.qop", spnegoDelegationQop);
+ // Note: Subject already set in SPNEGO authenticator so no need
+ // for Subject.doAs() here
+ }
+@@ -2421,23 +2351,20 @@ public class JNDIRealm extends RealmBase
+ }
+ } finally {
+ if (gssCredential != null && isUseDelegatedCredential()) {
+- restoreEnvironmentParameter(context,
+- Context.SECURITY_AUTHENTICATION, preservedEnvironment);
+- restoreEnvironmentParameter(context,
+- "javax.security.sasl.server.authentication", preservedEnvironment);
+- restoreEnvironmentParameter(context, "javax.security.sasl.qop",
+- preservedEnvironment);
++ restoreEnvironmentParameter(context, Context.SECURITY_AUTHENTICATION, preservedEnvironment);
++ restoreEnvironmentParameter(context, "javax.security.sasl.server.authentication", preservedEnvironment);
++ restoreEnvironmentParameter(context, "javax.security.sasl.qop", preservedEnvironment);
+ }
+ }
+
+ if (user != null) {
+- return new GenericPrincipal(user.getUserName(), user.getPassword(),
+- roles, null, null, gssCredential);
++ return new GenericPrincipal(user.getUserName(), user.getPassword(), roles, null, null, gssCredential);
+ }
+
+ return null;
+ }
+
++
+ private void restoreEnvironmentParameter(DirContext context,
+ String parameterName, Hashtable, ?> preservedEnvironment) {
+ try {
+@@ -2451,6 +2378,7 @@ public class JNDIRealm extends RealmBase
+ }
+ }
+
++
+ /**
+ * Open (if necessary) and return a connection to the configured
+ * directory server for this Realm.
+@@ -2458,12 +2386,28 @@ public class JNDIRealm extends RealmBase
+ * @exception NamingException if a directory server error occurs
+ */
+ protected JNDIConnection get() throws NamingException {
++ return get(false);
++ }
++
++ /**
++ * Open (if necessary) and return a connection to the configured
++ * directory server for this Realm.
++ * @param create when pooling, this forces creation of a new connection,
++ * for example after an error
++ * @return the connection
++ * @exception NamingException if a directory server error occurs
++ */
++ protected JNDIConnection get(boolean create) throws NamingException {
+ JNDIConnection connection = null;
+ // Use the pool if available, otherwise use the single connection
+ if (connectionPool != null) {
+- connection = connectionPool.pop();
+- if (connection == null) {
++ if (create) {
+ connection = create();
++ } else {
++ connection = connectionPool.pop();
++ if (connection == null) {
++ connection = create();
++ }
+ }
+ } else {
+ singleConnectionLock.lock();
+@@ -2475,6 +2419,7 @@ public class JNDIRealm extends RealmBase
+ return connection;
+ }
+
++
+ /**
+ * Release our use of this connection so that it can be recycled.
+ *
+@@ -2491,6 +2436,7 @@ public class JNDIRealm extends RealmBase
+ }
+ }
+
++
+ /**
+ * Create a new connection wrapper, along with the
+ * message formats.
+@@ -2505,8 +2451,7 @@ public class JNDIRealm extends RealmBase
+ int len = userPatternArray.length;
+ connection.userPatternFormatArray = new MessageFormat[len];
+ for (int i = 0; i < len; i++) {
+- connection.userPatternFormatArray[i] =
+- new MessageFormat(userPatternArray[i]);
++ connection.userPatternFormatArray[i] = new MessageFormat(userPatternArray[i]);
+ }
+ }
+ if (roleBase != null) {
+@@ -2518,6 +2463,7 @@ public class JNDIRealm extends RealmBase
+ return connection;
+ }
+
++
+ /**
+ * Create a new connection to the directory server.
+ * @param connection The directory server connection wrapper
+@@ -2552,12 +2498,14 @@ public class JNDIRealm extends RealmBase
+ }
+ }
+
++
+ @Override
+ public boolean isAvailable() {
+ // Simple best effort check
+ return (connectionPool != null || singleConnection.context != null);
+ }
+
++
+ private DirContext createDirContext(Hashtable env) throws NamingException {
+ if (useStartTls) {
+ return createTlsDirContext(env);
+@@ -2566,13 +2514,13 @@ public class JNDIRealm extends RealmBase
+ }
+ }
+
++
+ private SSLSocketFactory getSSLSocketFactory() {
+ if (sslSocketFactory != null) {
+ return sslSocketFactory;
+ }
+ final SSLSocketFactory result;
+- if (this.sslSocketFactoryClassName != null
+- && !sslSocketFactoryClassName.trim().equals("")) {
++ if (this.sslSocketFactoryClassName != null && !sslSocketFactoryClassName.trim().equals("")) {
+ result = createSSLSocketFactoryFromClassName(this.sslSocketFactoryClassName);
+ } else {
+ result = createSSLContextFactoryFromProtocol(sslProtocol);
+@@ -2581,6 +2529,7 @@ public class JNDIRealm extends RealmBase
+ return result;
+ }
+
++
+ private SSLSocketFactory createSSLSocketFactoryFromClassName(String className) {
+ try {
+ Object o = constructInstance(className);
+@@ -2598,6 +2547,7 @@ public class JNDIRealm extends RealmBase
+ }
+ }
+
++
+ private SSLSocketFactory createSSLContextFactoryFromProtocol(String protocol) {
+ try {
+ SSLContext sslContext;
+@@ -2609,14 +2559,13 @@ public class JNDIRealm extends RealmBase
+ }
+ return sslContext.getSocketFactory();
+ } catch (NoSuchAlgorithmException | KeyManagementException e) {
+- List allowedProtocols = Arrays
+- .asList(getSupportedSslProtocols());
+- throw new IllegalArgumentException(
+- sm.getString("jndiRealm.invalidSslProtocol", protocol,
+- allowedProtocols), e);
++ List allowedProtocols = Arrays.asList(getSupportedSslProtocols());
++ throw new IllegalArgumentException(sm.getString("jndiRealm.invalidSslProtocol",
++ protocol, allowedProtocols), e);
+ }
+ }
+
++
+ /**
+ * Create a tls enabled LdapContext and set the StartTlsResponse tls
+ * instance variable.
+@@ -2627,12 +2576,10 @@ public class JNDIRealm extends RealmBase
+ * @throws NamingException
+ * when something goes wrong while negotiating the connection
+ */
+- private DirContext createTlsDirContext(
+- Hashtable env) throws NamingException {
++ private DirContext createTlsDirContext(Hashtable env) throws NamingException {
+ Map savedEnv = new HashMap<>();
+- for (String key : Arrays.asList(Context.SECURITY_AUTHENTICATION,
+- Context.SECURITY_CREDENTIALS, Context.SECURITY_PRINCIPAL,
+- Context.SECURITY_PROTOCOL)) {
++ for (String key : Arrays.asList(Context.SECURITY_AUTHENTICATION, Context.SECURITY_CREDENTIALS,
++ Context.SECURITY_PRINCIPAL, Context.SECURITY_PROTOCOL)) {
+ Object entry = env.remove(key);
+ if (entry != null) {
+ savedEnv.put(key, entry);
+@@ -2641,8 +2588,7 @@ public class JNDIRealm extends RealmBase
+ LdapContext result = null;
+ try {
+ result = new InitialLdapContext(env, null);
+- tls = (StartTlsResponse) result
+- .extendedOperation(new StartTlsRequest());
++ tls = (StartTlsResponse) result.extendedOperation(new StartTlsRequest());
+ if (getHostnameVerifier() != null) {
+ tls.setHostnameVerifier(getHostnameVerifier());
+ }
+@@ -2651,22 +2597,21 @@ public class JNDIRealm extends RealmBase
+ }
+ try {
+ SSLSession negotiate = tls.negotiate(getSSLSocketFactory());
+- containerLog.debug(sm.getString("jndiRealm.negotiatedTls",
+- negotiate.getProtocol()));
++ containerLog.debug(sm.getString("jndiRealm.negotiatedTls", negotiate.getProtocol()));
+ } catch (IOException e) {
+ throw new NamingException(e.getMessage());
+ }
+ } finally {
+ if (result != null) {
+ for (Map.Entry savedEntry : savedEnv.entrySet()) {
+- result.addToEnvironment(savedEntry.getKey(),
+- savedEntry.getValue());
++ result.addToEnvironment(savedEntry.getKey(), savedEntry.getValue());
+ }
+ }
+ }
+ return result;
+ }
+
++
+ /**
+ * Create our directory context configuration.
+ *
+@@ -2677,40 +2622,48 @@ public class JNDIRealm extends RealmBase
+ Hashtable env = new Hashtable<>();
+
+ // Configure our directory context environment.
+- if (containerLog.isDebugEnabled() && connectionAttempt == 0)
++ if (containerLog.isDebugEnabled() && connectionAttempt == 0) {
+ containerLog.debug("Connecting to URL " + connectionURL);
+- else if (containerLog.isDebugEnabled() && connectionAttempt > 0)
++ } else if (containerLog.isDebugEnabled() && connectionAttempt > 0) {
+ containerLog.debug("Connecting to URL " + alternateURL);
++ }
+ env.put(Context.INITIAL_CONTEXT_FACTORY, contextFactory);
+- if (connectionName != null)
++ if (connectionName != null) {
+ env.put(Context.SECURITY_PRINCIPAL, connectionName);
+- if (connectionPassword != null)
++ }
++ if (connectionPassword != null) {
+ env.put(Context.SECURITY_CREDENTIALS, connectionPassword);
+- if (connectionURL != null && connectionAttempt == 0)
++ }
++ if (connectionURL != null && connectionAttempt == 0) {
+ env.put(Context.PROVIDER_URL, connectionURL);
+- else if (alternateURL != null && connectionAttempt > 0)
++ } else if (alternateURL != null && connectionAttempt > 0) {
+ env.put(Context.PROVIDER_URL, alternateURL);
+- if (authentication != null)
++ }
++ if (authentication != null) {
+ env.put(Context.SECURITY_AUTHENTICATION, authentication);
+- if (protocol != null)
++ }
++ if (protocol != null) {
+ env.put(Context.SECURITY_PROTOCOL, protocol);
+- if (referrals != null)
++ }
++ if (referrals != null) {
+ env.put(Context.REFERRAL, referrals);
+- if (derefAliases != null)
++ }
++ if (derefAliases != null) {
+ env.put(JNDIRealm.DEREF_ALIASES, derefAliases);
+- if (connectionTimeout != null)
++ }
++ if (connectionTimeout != null) {
+ env.put("com.sun.jndi.ldap.connect.timeout", connectionTimeout);
+- if (readTimeout != null)
++ }
++ if (readTimeout != null) {
+ env.put("com.sun.jndi.ldap.read.timeout", readTimeout);
++ }
+
+ return env;
+-
+ }
+
+
+ // ------------------------------------------------------ Lifecycle Methods
+
+-
+ /**
+ * Prepare for the beginning of active use of the public methods of this
+ * component and implement the requirements of
+@@ -2752,7 +2705,7 @@ public class JNDIRealm extends RealmBase
+ * @exception LifecycleException if this component detects a fatal error
+ * that needs to be reported
+ */
+- @Override
++ @Override
+ protected void stopInternal() throws LifecycleException {
+ super.stopInternal();
+ // Close any open directory server connection
+@@ -2765,6 +2718,7 @@ public class JNDIRealm extends RealmBase
+ }
+ }
+
++
+ /**
+ * Given a string containing LDAP patterns for user locations (separated by
+ * parentheses in a pseudo-LDAP search string format -
+@@ -2799,8 +2753,7 @@ public class JNDIRealm extends RealmBase
+ while (userPatternString.charAt(endParenLoc - 1) == '\\') {
+ endParenLoc = userPatternString.indexOf(')', endParenLoc+1);
+ }
+- String nextPathPart = userPatternString.substring
+- (startParenLoc+1, endParenLoc);
++ String nextPathPart = userPatternString.substring(startParenLoc+1, endParenLoc);
+ pathList.add(nextPathPart);
+ startingPoint = endParenLoc+1;
+ startParenLoc = userPatternString.indexOf('(', startingPoint);
+@@ -2808,7 +2761,6 @@ public class JNDIRealm extends RealmBase
+ return pathList.toArray(new String[] {});
+ }
+ return null;
+-
+ }
+
+
+@@ -2823,10 +2775,36 @@ public class JNDIRealm extends RealmBase
+ * ) -> \29
+ * \ -> \5c
+ * \0 -> \00
++ *
+ * @param inString string to escape according to RFC 2254 guidelines
++ *
+ * @return String the escaped/encoded result
++ *
++ * @deprecated Will be removed in Tomcat 10.1.x onwards
+ */
++ @Deprecated
+ protected String doRFC2254Encoding(String inString) {
++ return doFilterEscaping(inString);
++ }
++
++
++ /**
++ * Given an LDAP search string, returns the string with certain characters
++ * escaped according to RFC 2254 guidelines.
++ * The character mapping is as follows:
++ * char -> Replacement
++ * ---------------------------
++ * * -> \2a
++ * ( -> \28
++ * ) -> \29
++ * \ -> \5c
++ * \0 -> \00
++ *
++ * @param inString string to escape according to RFC 2254 guidelines
++ *
++ * @return String the escaped/encoded result
++ */
++ protected String doFilterEscaping(String inString) {
+ StringBuilder buf = new StringBuilder(inString.length());
+ for (int i = 0; i < inString.length(); i++) {
+ char c = inString.charAt(i);
+@@ -2864,47 +2842,42 @@ public class JNDIRealm extends RealmBase
+ * @return String containing the distinguished name
+ * @exception NamingException if a directory server error occurs
+ */
+- protected String getDistinguishedName(DirContext context, String base,
+- SearchResult result) throws NamingException {
++ protected String getDistinguishedName(DirContext context, String base, SearchResult result) throws NamingException {
+ // Get the entry's distinguished name. For relative results, this means
+ // we need to composite a name with the base name, the context name, and
+ // the result name. For non-relative names, use the returned name.
+ String resultName = result.getName();
+ Name name;
+ if (result.isRelative()) {
+- if (containerLog.isTraceEnabled()) {
+- containerLog.trace(" search returned relative name: " + resultName);
+- }
+- NameParser parser = context.getNameParser("");
+- Name contextName = parser.parse(context.getNameInNamespace());
+- Name baseName = parser.parse(base);
++ if (containerLog.isTraceEnabled()) {
++ containerLog.trace(" search returned relative name: " + resultName);
++ }
++ NameParser parser = context.getNameParser("");
++ Name contextName = parser.parse(context.getNameInNamespace());
++ Name baseName = parser.parse(base);
+
+- // Bugzilla 32269
+- Name entryName = parser.parse(new CompositeName(resultName).get(0));
++ // Bugzilla 32269
++ Name entryName = parser.parse(new CompositeName(resultName).get(0));
+
+- name = contextName.addAll(baseName);
+- name = name.addAll(entryName);
++ name = contextName.addAll(baseName);
++ name = name.addAll(entryName);
+ } else {
+- if (containerLog.isTraceEnabled()) {
+- containerLog.trace(" search returned absolute name: " + resultName);
+- }
+- try {
+- // Normalize the name by running it through the name parser.
+- NameParser parser = context.getNameParser("");
+- URI userNameUri = new URI(resultName);
+- String pathComponent = userNameUri.getPath();
+- // Should not ever have an empty path component, since that is /{DN}
+- if (pathComponent.length() < 1 ) {
+- throw new InvalidNameException(
+- "Search returned unparseable absolute name: " +
+- resultName );
+- }
+- name = parser.parse(pathComponent.substring(1));
+- } catch ( URISyntaxException e ) {
+- throw new InvalidNameException(
+- "Search returned unparseable absolute name: " +
+- resultName );
+- }
++ if (containerLog.isTraceEnabled()) {
++ containerLog.trace(" search returned absolute name: " + resultName);
++ }
++ try {
++ // Normalize the name by running it through the name parser.
++ NameParser parser = context.getNameParser("");
++ URI userNameUri = new URI(resultName);
++ String pathComponent = userNameUri.getPath();
++ // Should not ever have an empty path component, since that is /{DN}
++ if (pathComponent.length() < 1 ) {
++ throw new InvalidNameException("Search returned unparseable absolute name: " + resultName);
++ }
++ name = parser.parse(pathComponent.substring(1));
++ } catch ( URISyntaxException e ) {
++ throw new InvalidNameException("Search returned unparseable absolute name: " + resultName);
++ }
+ }
+
+ if (getForceDnHexEscape()) {
+@@ -2916,6 +2889,78 @@ public class JNDIRealm extends RealmBase
+ }
+
+
++ /**
++ * Implements the necessary escaping to represent an attribute value as a
++ * String as per RFC 4514.
++ *
++ * @param input The original attribute value
++ * @return The string representation of the attribute value
++ */
++ protected String doAttributeValueEscaping(String input) {
++ int len = input.length();
++ StringBuilder result = new StringBuilder();
++
++ for (int i = 0; i < len; i++) {
++ char c = input.charAt(i);
++ switch (c) {
++ case ' ': {
++ if (i == 0 || i == (len -1)) {
++ result.append("\\20");
++ } else {
++ result.append(c);
++ }
++ break;
++ }
++ case '#': {
++ if (i == 0 ) {
++ result.append("\\23");
++ } else {
++ result.append(c);
++ }
++ break;
++ }
++ case '\"': {
++ result.append("\\22");
++ break;
++ }
++ case '+': {
++ result.append("\\2B");
++ break;
++ }
++ case ',': {
++ result.append("\\2C");
++ break;
++ }
++ case ';': {
++ result.append("\\3B");
++ break;
++ }
++ case '<': {
++ result.append("\\3C");
++ break;
++ }
++ case '>': {
++ result.append("\\3E");
++ break;
++ }
++ case '\\': {
++ result.append("\\5C");
++ break;
++ }
++ case '\u0000': {
++ result.append("\\00");
++ break;
++ }
++ default:
++ result.append(c);
++ }
++
++ }
++
++ return result.toString();
++ }
++
++
+ protected static String convertToHexEscape(String input) {
+ if (input.indexOf('\\') == -1) {
+ // No escaping present. Return original.
+@@ -2992,7 +3037,7 @@ public class JNDIRealm extends RealmBase
+ }
+
+
+- // ------------------------------------------------------ Private Classes
++ // ------------------------------------------------------ Protected Classes
+
+ /**
+ * A protected class representing a User
+@@ -3005,9 +3050,7 @@ public class JNDIRealm extends RealmBase
+ private final List roles;
+ private final String userRoleId;
+
+-
+- public User(String username, String dn, String password,
+- List roles, String userRoleId) {
++ public User(String username, String dn, String password, List roles, String userRoleId) {
+ this.username = username;
+ this.dn = dn;
+ this.password = password;
+@@ -3040,6 +3083,7 @@ public class JNDIRealm extends RealmBase
+ }
+ }
+
++
+ /**
+ * Class holding the connection to the directory plus the associated
+ * non thread safe message formats.
+@@ -3074,8 +3118,5 @@ public class JNDIRealm extends RealmBase
+ * The directory context linking us to our directory server.
+ */
+ protected DirContext context = null;
+-
+ }
+-
+ }
+-
+Index: apache-tomcat-9.0.43-src/test/org/apache/catalina/realm/TestJNDIRealmAttributeValueEscape.java
+===================================================================
+--- /dev/null
++++ apache-tomcat-9.0.43-src/test/org/apache/catalina/realm/TestJNDIRealmAttributeValueEscape.java
+@@ -0,0 +1,88 @@
++
++@@ -0,0 +1,86 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++package org.apache.catalina.realm;
++
++import java.util.ArrayList;
++import java.util.Collection;
++import java.util.List;
++
++import org.junit.Assert;
++import org.junit.Test;
++import org.junit.runner.RunWith;
++import org.junit.runners.Parameterized;
++import org.junit.runners.Parameterized.Parameter;
++
++@RunWith(Parameterized.class)
++public class TestJNDIRealmAttributeValueEscape {
++
++ @Parameterized.Parameters(name = "{index}: in[{0}], out[{1}]")
++ public static Collection