001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2008-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2013-2016 ForgeRock AS. 016 */ 017package org.opends.admin.ads.util; 018 019import java.util.LinkedHashSet; 020import java.util.Map; 021import java.util.Set; 022 023import javax.naming.AuthenticationException; 024import javax.naming.NamingException; 025import javax.naming.NoPermissionException; 026import javax.naming.TimeLimitExceededException; 027import javax.naming.ldap.LdapName; 028 029import org.forgerock.i18n.LocalizableMessage; 030import org.forgerock.i18n.slf4j.LocalizedLogger; 031import org.opends.admin.ads.ADSContext; 032import org.opends.admin.ads.ADSContext.ServerProperty; 033import org.opends.admin.ads.ServerDescriptor; 034import org.opends.admin.ads.TopologyCacheException; 035import org.opends.admin.ads.TopologyCacheException.Type; 036import org.opends.admin.ads.TopologyCacheFilter; 037 038import com.forgerock.opendj.cli.Utils; 039 040/** 041 * Class used to load the configuration of a server. Basically the code 042 * uses some provided properties and authentication information to connect 043 * to the server and then generate a ServerDescriptor object based on the 044 * read configuration. 045 */ 046public class ServerLoader extends Thread 047{ 048 private final Map<ServerProperty, Object> serverProperties; 049 private boolean isOver; 050 private boolean isInterrupted; 051 private String lastLdapUrl; 052 private TopologyCacheException lastException; 053 private ServerDescriptor serverDescriptor; 054 private final ApplicationTrustManager trustManager; 055 private final int timeout; 056 private final String dn; 057 private final String pwd; 058 private final LinkedHashSet<PreferredConnection> preferredLDAPURLs; 059 private final TopologyCacheFilter filter; 060 061 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 062 063 /** 064 * Constructor. 065 * @param serverProperties the server properties of the server we want to 066 * load. 067 * @param dn the DN that we must use to bind to the server. 068 * @param pwd the password that we must use to bind to the server. 069 * @param trustManager the ApplicationTrustManager to be used when we try 070 * to connect to the server. 071 * @param timeout the timeout to establish the connection in milliseconds. 072 * Use {@code 0} to express no timeout. 073 * @param preferredLDAPURLs the list of preferred LDAP URLs that we want 074 * to use to connect to the server. They will be used only if they correspond 075 * to the URLs that we found in the the server properties. 076 * @param filter the topology cache filter to be used. This can be used not 077 * to retrieve all the information. 078 */ 079 public ServerLoader(Map<ServerProperty,Object> serverProperties, 080 String dn, String pwd, ApplicationTrustManager trustManager, 081 int timeout, 082 Set<PreferredConnection> preferredLDAPURLs, 083 TopologyCacheFilter filter) 084 { 085 this.serverProperties = serverProperties; 086 this.dn = dn; 087 this.pwd = pwd; 088 this.trustManager = trustManager; 089 this.timeout = timeout; 090 this.preferredLDAPURLs = new LinkedHashSet<>(preferredLDAPURLs); 091 this.filter = filter; 092 } 093 094 /** 095 * Returns the ServerDescriptor that could be retrieved. 096 * @return the ServerDescriptor that could be retrieved. 097 */ 098 public ServerDescriptor getServerDescriptor() 099 { 100 if (serverDescriptor == null) 101 { 102 serverDescriptor = ServerDescriptor.createStandalone(serverProperties); 103 } 104 serverDescriptor.setLastException(lastException); 105 return serverDescriptor; 106 } 107 108 /** 109 * Returns the last exception that occurred while trying to generate 110 * the ServerDescriptor object. 111 * @return the last exception that occurred while trying to generate 112 * the ServerDescriptor object. 113 */ 114 public TopologyCacheException getLastException() 115 { 116 return lastException; 117 } 118 119 @Override 120 public void interrupt() 121 { 122 if (!isOver) 123 { 124 isInterrupted = true; 125 String ldapUrl = lastLdapUrl; 126 if (ldapUrl == null) 127 { 128 LinkedHashSet<PreferredConnection> urls = getLDAPURLsByPreference(); 129 if (!urls.isEmpty()) 130 { 131 ldapUrl = urls.iterator().next().getLDAPURL(); 132 } 133 } 134 lastException = new TopologyCacheException( 135 TopologyCacheException.Type.TIMEOUT, 136 new TimeLimitExceededException("Timeout reading server: "+ldapUrl), 137 trustManager, ldapUrl); 138 logger.warn(LocalizableMessage.raw("Timeout reading server: "+ldapUrl)); 139 } 140 super.interrupt(); 141 } 142 143 /** The method where we try to generate the ServerDescriptor object. */ 144 @Override 145 public void run() 146 { 147 lastException = null; 148 boolean connCreated = false; 149 try (ConnectionWrapper conn = createConnectionWrapper()) 150 { 151 connCreated = true; 152 serverDescriptor = ServerDescriptor.createStandalone(conn.getLdapContext(), filter); 153 serverDescriptor.setAdsProperties(serverProperties); 154 serverDescriptor.updateAdsPropertiesWithServerProperties(); 155 } 156 catch (NoPermissionException e) 157 { 158 logger.warn(LocalizableMessage.raw("Permissions error reading server: " + lastLdapUrl, e)); 159 Type type = isAdministratorDn() 160 ? TopologyCacheException.Type.NO_PERMISSIONS 161 : TopologyCacheException.Type.NOT_GLOBAL_ADMINISTRATOR; 162 lastException = new TopologyCacheException(type, e, trustManager, lastLdapUrl); 163 } 164 catch (AuthenticationException e) 165 { 166 logger.warn(LocalizableMessage.raw("Authentication exception: " + lastLdapUrl, e)); 167 Type type = isAdministratorDn() 168 ? TopologyCacheException.Type.GENERIC_READING_SERVER 169 : TopologyCacheException.Type.NOT_GLOBAL_ADMINISTRATOR; 170 lastException = new TopologyCacheException(type, e, trustManager, lastLdapUrl); 171 } 172 catch (NamingException e) 173 { 174 logger.warn(LocalizableMessage.raw("NamingException error reading server: " + lastLdapUrl, e)); 175 Type type = connCreated 176 ? TopologyCacheException.Type.GENERIC_READING_SERVER 177 : TopologyCacheException.Type.GENERIC_CREATING_CONNECTION; 178 lastException = new TopologyCacheException(type, e, trustManager, lastLdapUrl); 179 } 180 catch (Throwable t) 181 { 182 if (!isInterrupted) 183 { 184 logger.warn(LocalizableMessage.raw("Generic error reading server: " + lastLdapUrl, t)); 185 logger.warn(LocalizableMessage.raw("server Properties: " + serverProperties)); 186 lastException = new TopologyCacheException(TopologyCacheException.Type.BUG, t); 187 } 188 } 189 finally 190 { 191 isOver = true; 192 } 193 } 194 195 /** 196 * Returns a Connection Wrapper. 197 * 198 * @return the connection wrapper 199 * @throws NamingException 200 * If an error occurs. 201 */ 202 public ConnectionWrapper createConnectionWrapper() throws NamingException 203 { 204 if (trustManager != null) 205 { 206 trustManager.resetLastRefusedItems(); 207 208 String host = (String)serverProperties.get(ServerProperty.HOST_NAME); 209 trustManager.setHost(host); 210 } 211 212 /* Try to connect to the server in a certain order of preference. If an 213 * URL fails, we will try with the others. 214 */ 215 for (PreferredConnection connection : getLDAPURLsByPreference()) 216 { 217 lastLdapUrl = connection.getLDAPURL(); 218 ConnectionWrapper conn = new ConnectionWrapper(lastLdapUrl, connection.getType(), dn, pwd, timeout, trustManager); 219 if (conn.getLdapContext() != null) 220 { 221 return conn; 222 } 223 } 224 return null; 225 } 226 227 /** 228 * Returns the non-secure LDAP URL for the given server properties. It 229 * returns NULL if according to the server properties no non-secure LDAP URL 230 * can be generated (LDAP disabled or port not defined). 231 * @param serverProperties the server properties to be used to generate 232 * the non-secure LDAP URL. 233 * @return the non-secure LDAP URL for the given server properties. 234 */ 235 private String getLdapUrl(Map<ServerProperty,Object> serverProperties) 236 { 237 if (isLdapEnabled(serverProperties)) 238 { 239 return "ldap://" + getHostNameForLdapUrl(serverProperties) + ":" 240 + serverProperties.get(ServerProperty.LDAP_PORT); 241 } 242 return null; 243 } 244 245 /** 246 * Returns the StartTLS LDAP URL for the given server properties. It 247 * returns NULL if according to the server properties no StartTLS LDAP URL 248 * can be generated (StartTLS disabled or port not defined). 249 * @param serverProperties the server properties to be used to generate 250 * the StartTLS LDAP URL. 251 * @return the StartTLS LDAP URL for the given server properties. 252 */ 253 private String getStartTlsLdapUrl(Map<ServerProperty,Object> serverProperties) 254 { 255 if (isStartTlsEnabled(serverProperties)) 256 { 257 return "ldap://" + getHostNameForLdapUrl(serverProperties) + ":" 258 + serverProperties.get(ServerProperty.LDAP_PORT); 259 } 260 return null; 261 } 262 263 /** 264 * Returns the LDAPs URL for the given server properties. It 265 * returns NULL if according to the server properties no LDAPS URL 266 * can be generated (LDAPS disabled or port not defined). 267 * @param serverProperties the server properties to be used to generate 268 * the LDAPS URL. 269 * @return the LDAPS URL for the given server properties. 270 */ 271 private String getLdapsUrl(Map<ServerProperty,Object> serverProperties) 272 { 273 if (isLdapsEnabled(serverProperties)) 274 { 275 return "ldaps://" + getHostNameForLdapUrl(serverProperties) + ":" 276 + serverProperties.get(ServerProperty.LDAPS_PORT); 277 } 278 return null; 279 } 280 281 /** 282 * Returns the administration connector URL for the given server properties. 283 * It returns NULL if according to the server properties no administration 284 * connector URL can be generated. 285 * @param serverProperties the server properties to be used to generate 286 * the administration connector URL. 287 * @return the administration connector URL for the given server properties. 288 */ 289 private String getAdminConnectorUrl( 290 Map<ServerProperty,Object> serverProperties) 291 { 292 if (isPropertyEnabled(serverProperties, ServerProperty.ADMIN_ENABLED)) 293 { 294 Object adminPort = serverProperties.get(ServerProperty.ADMIN_PORT); 295 if (adminPort != null) 296 { 297 return "ldaps://" + getHostNameForLdapUrl(serverProperties) + ":" + adminPort; 298 } 299 } 300 return null; 301 } 302 303 private boolean isLdapEnabled(Map<ServerProperty, Object> serverProperties) 304 { 305 return isPropertyEnabled(serverProperties, ServerProperty.LDAP_ENABLED); 306 } 307 308 private boolean isLdapsEnabled(Map<ServerProperty, Object> serverProperties) 309 { 310 return isPropertyEnabled(serverProperties, ServerProperty.LDAPS_ENABLED); 311 } 312 313 private boolean isStartTlsEnabled(Map<ServerProperty, Object> serverProperties) 314 { 315 return isLdapEnabled(serverProperties) && isPropertyEnabled(serverProperties, ServerProperty.STARTTLS_ENABLED); 316 } 317 318 private boolean isPropertyEnabled(Map<ServerProperty, Object> serverProperties, ServerProperty property) 319 { 320 Object v = serverProperties.get(property); 321 return v != null && "true".equalsIgnoreCase(v.toString()); 322 } 323 324 /** 325 * Returns the host name to be used to generate an LDAP URL based on the 326 * contents of the provided server properties. 327 * @param serverProperties the server properties. 328 * @return the host name to be used to generate an LDAP URL based on the 329 * contents of the provided server properties. 330 */ 331 private String getHostNameForLdapUrl( 332 Map<ServerProperty,Object> serverProperties) 333 { 334 String host = (String)serverProperties.get(ServerProperty.HOST_NAME); 335 return Utils.getHostNameForLdapUrl(host); 336 } 337 338 /** 339 * Returns whether the DN provided in the constructor is a Global 340 * Administrator DN or not. 341 * @return <CODE>true</CODE> if the DN provided in the constructor is a Global 342 * Administrator DN and <CODE>false</CODE> otherwise. 343 */ 344 private boolean isAdministratorDn() 345 { 346 try 347 { 348 LdapName theDn = new LdapName(dn); 349 LdapName containerDn = 350 new LdapName(ADSContext.getAdministratorContainerDN()); 351 return theDn.startsWith(containerDn); 352 } 353 catch (Throwable t) 354 { 355 logger.warn(LocalizableMessage.raw("Error parsing authentication DNs.", t)); 356 return false; 357 } 358 } 359 360 /** 361 * Returns the list of LDAP URLs that can be used to connect to the server. 362 * They are ordered so that the first URL is the preferred URL to be used. 363 * @return the list of LDAP URLs that can be used to connect to the server. 364 * They are ordered so that the first URL is the preferred URL to be used. 365 */ 366 private LinkedHashSet<PreferredConnection> getLDAPURLsByPreference() 367 { 368 LinkedHashSet<PreferredConnection> ldapUrls = new LinkedHashSet<>(); 369 370 String adminConnectorUrl = getAdminConnectorUrl(serverProperties); 371 String ldapsUrl = getLdapsUrl(serverProperties); 372 String startTLSUrl = getStartTlsLdapUrl(serverProperties); 373 String ldapUrl = getLdapUrl(serverProperties); 374 375 // Check the preferred connections passed in the constructor. 376 for (PreferredConnection connection : preferredLDAPURLs) 377 { 378 String url = connection.getLDAPURL(); 379 if (url.equalsIgnoreCase(adminConnectorUrl)) 380 { 381 ldapUrls.add(connection); 382 } 383 else if (url.equalsIgnoreCase(ldapsUrl) && 384 connection.getType() == PreferredConnection.Type.LDAPS) 385 { 386 ldapUrls.add(connection); 387 } 388 else if (url.equalsIgnoreCase(startTLSUrl) && 389 connection.getType() == PreferredConnection.Type.START_TLS) 390 { 391 ldapUrls.add(connection); 392 } 393 else if (url.equalsIgnoreCase(ldapUrl) && 394 connection.getType() == PreferredConnection.Type.LDAP) 395 { 396 ldapUrls.add(connection); 397 } 398 } 399 400 if (adminConnectorUrl != null) 401 { 402 ldapUrls.add(new PreferredConnection(adminConnectorUrl, PreferredConnection.Type.LDAPS)); 403 } 404 if (ldapsUrl != null) 405 { 406 ldapUrls.add(new PreferredConnection(ldapsUrl, PreferredConnection.Type.LDAPS)); 407 } 408 if (startTLSUrl != null) 409 { 410 ldapUrls.add(new PreferredConnection(startTLSUrl, PreferredConnection.Type.START_TLS)); 411 } 412 if (ldapUrl != null) 413 { 414 ldapUrls.add(new PreferredConnection(ldapUrl, PreferredConnection.Type.LDAP)); 415 } 416 return ldapUrls; 417 } 418}