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-2015 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.InitialLdapContext; 028import javax.naming.ldap.LdapName; 029 030import org.forgerock.i18n.LocalizableMessage; 031import org.forgerock.i18n.slf4j.LocalizedLogger; 032import org.opends.admin.ads.ADSContext; 033import org.opends.admin.ads.ADSContext.ServerProperty; 034import org.opends.admin.ads.ServerDescriptor; 035import org.opends.admin.ads.TopologyCacheException; 036import org.opends.admin.ads.TopologyCacheException.Type; 037import org.opends.admin.ads.TopologyCacheFilter; 038 039import com.forgerock.opendj.cli.Utils; 040 041import static org.opends.server.util.StaticUtils.*; 042 043/** 044 * Class used to load the configuration of a server. Basically the code 045 * uses some provided properties and authentication information to connect 046 * to the server and then generate a ServerDescriptor object based on the 047 * read configuration. 048 */ 049public class ServerLoader extends Thread 050{ 051 private Map<ServerProperty,Object> serverProperties; 052 private boolean isOver; 053 private boolean isInterrupted; 054 private String lastLdapUrl; 055 private TopologyCacheException lastException; 056 private ServerDescriptor serverDescriptor; 057 private ApplicationTrustManager trustManager; 058 private int timeout; 059 private String dn; 060 private String pwd; 061 private final LinkedHashSet<PreferredConnection> preferredLDAPURLs; 062 private TopologyCacheFilter filter; 063 064 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 065 066 /** 067 * Constructor. 068 * @param serverProperties the server properties of the server we want to 069 * load. 070 * @param dn the DN that we must use to bind to the server. 071 * @param pwd the password that we must use to bind to the server. 072 * @param trustManager the ApplicationTrustManager to be used when we try 073 * to connect to the server. 074 * @param timeout the timeout to establish the connection in milliseconds. 075 * Use {@code 0} to express no timeout. 076 * @param preferredLDAPURLs the list of preferred LDAP URLs that we want 077 * to use to connect to the server. They will be used only if they correspond 078 * to the URLs that we found in the the server properties. 079 * @param filter the topology cache filter to be used. This can be used not 080 * to retrieve all the information. 081 */ 082 public ServerLoader(Map<ServerProperty,Object> serverProperties, 083 String dn, String pwd, ApplicationTrustManager trustManager, 084 int timeout, 085 Set<PreferredConnection> preferredLDAPURLs, 086 TopologyCacheFilter filter) 087 { 088 this.serverProperties = serverProperties; 089 this.dn = dn; 090 this.pwd = pwd; 091 this.trustManager = trustManager; 092 this.timeout = timeout; 093 this.preferredLDAPURLs = new LinkedHashSet<>(preferredLDAPURLs); 094 this.filter = filter; 095 } 096 097 /** 098 * Returns the ServerDescriptor that could be retrieved. 099 * @return the ServerDescriptor that could be retrieved. 100 */ 101 public ServerDescriptor getServerDescriptor() 102 { 103 if (serverDescriptor == null) 104 { 105 serverDescriptor = ServerDescriptor.createStandalone(serverProperties); 106 } 107 serverDescriptor.setLastException(lastException); 108 return serverDescriptor; 109 } 110 111 /** 112 * Returns the last exception that occurred while trying to generate 113 * the ServerDescriptor object. 114 * @return the last exception that occurred while trying to generate 115 * the ServerDescriptor object. 116 */ 117 public TopologyCacheException getLastException() 118 { 119 return lastException; 120 } 121 122 /** {@inheritDoc} */ 123 @Override 124 public void interrupt() 125 { 126 if (!isOver) 127 { 128 isInterrupted = true; 129 String ldapUrl = getLastLdapUrl(); 130 if (ldapUrl == null) 131 { 132 LinkedHashSet<PreferredConnection> urls = getLDAPURLsByPreference(); 133 if (!urls.isEmpty()) 134 { 135 ldapUrl = urls.iterator().next().getLDAPURL(); 136 } 137 } 138 lastException = new TopologyCacheException( 139 TopologyCacheException.Type.TIMEOUT, 140 new TimeLimitExceededException("Timeout reading server: "+ldapUrl), 141 trustManager, ldapUrl); 142 logger.warn(LocalizableMessage.raw("Timeout reading server: "+ldapUrl)); 143 } 144 super.interrupt(); 145 } 146 147 /** 148 * The method where we try to generate the ServerDescriptor object. 149 */ 150 @Override 151 public void run() 152 { 153 lastException = null; 154 InitialLdapContext ctx = null; 155 try 156 { 157 ctx = createContext(); 158 serverDescriptor = ServerDescriptor.createStandalone(ctx, filter); 159 serverDescriptor.setAdsProperties(serverProperties); 160 serverDescriptor.updateAdsPropertiesWithServerProperties(); 161 } 162 catch (NoPermissionException npe) 163 { 164 logger.warn(LocalizableMessage.raw( 165 "Permissions error reading server: "+getLastLdapUrl(), npe)); 166 if (!isAdministratorDn()) 167 { 168 lastException = new TopologyCacheException( 169 TopologyCacheException.Type.NOT_GLOBAL_ADMINISTRATOR, npe, 170 trustManager, getLastLdapUrl()); 171 } 172 else 173 { 174 lastException = 175 new TopologyCacheException( 176 TopologyCacheException.Type.NO_PERMISSIONS, npe, 177 trustManager, getLastLdapUrl()); 178 } 179 } 180 catch (AuthenticationException ae) 181 { 182 logger.warn(LocalizableMessage.raw( 183 "Authentication exception: "+getLastLdapUrl(), ae)); 184 if (!isAdministratorDn()) 185 { 186 lastException = new TopologyCacheException( 187 TopologyCacheException.Type.NOT_GLOBAL_ADMINISTRATOR, ae, 188 trustManager, getLastLdapUrl()); 189 } 190 else 191 { 192 lastException = 193 new TopologyCacheException( 194 TopologyCacheException.Type.GENERIC_READING_SERVER, ae, 195 trustManager, getLastLdapUrl()); 196 } 197 } 198 catch (NamingException ne) 199 { 200 logger.warn(LocalizableMessage.raw( 201 "NamingException error reading server: "+getLastLdapUrl(), ne)); 202 Type type = ctx == null 203 ? TopologyCacheException.Type.GENERIC_CREATING_CONNECTION 204 : TopologyCacheException.Type.GENERIC_READING_SERVER; 205 lastException = new TopologyCacheException( 206 type, ne, trustManager, getLastLdapUrl()); 207 } 208 catch (Throwable t) 209 { 210 if (!isInterrupted) 211 { 212 logger.warn(LocalizableMessage.raw( 213 "Generic error reading server: "+getLastLdapUrl(), t)); 214 logger.warn(LocalizableMessage.raw("server Properties: "+serverProperties)); 215 lastException = 216 new TopologyCacheException(TopologyCacheException.Type.BUG, t); 217 } 218 } 219 finally 220 { 221 isOver = true; 222 close(ctx); 223 } 224 } 225 226 /** 227 * Create an InitialLdapContext based in the provide server properties and 228 * authentication data provided in the constructor. 229 * @return an InitialLdapContext based in the provide server properties and 230 * authentication data provided in the constructor. 231 * @throws NamingException if an error occurred while creating the 232 * InitialLdapContext. 233 */ 234 public InitialLdapContext createContext() throws NamingException 235 { 236 InitialLdapContext ctx = null; 237 if (trustManager != null) 238 { 239 trustManager.resetLastRefusedItems(); 240 241 String host = (String)serverProperties.get(ServerProperty.HOST_NAME); 242 trustManager.setHost(host); 243 } 244 245 /* Try to connect to the server in a certain order of preference. If an 246 * URL fails, we will try with the others. 247 */ 248 LinkedHashSet<PreferredConnection> conns = getLDAPURLsByPreference(); 249 250 for (PreferredConnection connection : conns) 251 { 252 if (ctx == null) 253 { 254 lastLdapUrl = connection.getLDAPURL(); 255 switch (connection.getType()) 256 { 257 case LDAPS: 258 ctx = ConnectionUtils.createLdapsContext(lastLdapUrl, dn, pwd, 259 timeout, null, trustManager, 260 null); 261 break; 262 case START_TLS: 263 ctx = ConnectionUtils.createStartTLSContext(lastLdapUrl, dn, pwd, 264 timeout, null, trustManager, 265 null, null); 266 break; 267 default: 268 ctx = ConnectionUtils.createLdapContext(lastLdapUrl, dn, pwd, 269 timeout, null); 270 } 271 } 272 } 273 return ctx; 274 } 275 276 /** 277 * Returns the last LDAP URL to which we tried to connect. 278 * @return the last LDAP URL to which we tried to connect. 279 */ 280 private String getLastLdapUrl() 281 { 282 return lastLdapUrl; 283 } 284 285 /** 286 * Returns the non-secure LDAP URL for the given server properties. It 287 * returns NULL if according to the server properties no non-secure LDAP URL 288 * can be generated (LDAP disabled or port not defined). 289 * @param serverProperties the server properties to be used to generate 290 * the non-secure LDAP URL. 291 * @return the non-secure LDAP URL for the given server properties. 292 */ 293 private String getLdapUrl(Map<ServerProperty,Object> serverProperties) 294 { 295 if (isLdapEnabled(serverProperties)) 296 { 297 return "ldap://" + getHostNameForLdapUrl(serverProperties) + ":" 298 + serverProperties.get(ServerProperty.LDAP_PORT); 299 } 300 return null; 301 } 302 303 /** 304 * Returns the StartTLS LDAP URL for the given server properties. It 305 * returns NULL if according to the server properties no StartTLS LDAP URL 306 * can be generated (StartTLS disabled or port not defined). 307 * @param serverProperties the server properties to be used to generate 308 * the StartTLS LDAP URL. 309 * @return the StartTLS LDAP URL for the given server properties. 310 */ 311 private String getStartTlsLdapUrl(Map<ServerProperty,Object> serverProperties) 312 { 313 if (isLdapEnabled(serverProperties) && isStartTlsEnabled(serverProperties)) 314 { 315 return "ldap://" + getHostNameForLdapUrl(serverProperties) + ":" 316 + serverProperties.get(ServerProperty.LDAP_PORT); 317 } 318 return null; 319 } 320 321 /** 322 * Returns the LDAPs URL for the given server properties. It 323 * returns NULL if according to the server properties no LDAPS URL 324 * can be generated (LDAPS disabled or port not defined). 325 * @param serverProperties the server properties to be used to generate 326 * the LDAPS URL. 327 * @return the LDAPS URL for the given server properties. 328 */ 329 private String getLdapsUrl(Map<ServerProperty,Object> serverProperties) 330 { 331 boolean ldapsEnabled = isLdapsEnabled(serverProperties); 332 if (ldapsEnabled) 333 { 334 return "ldaps://" + getHostNameForLdapUrl(serverProperties) + ":" 335 + serverProperties.get(ServerProperty.LDAPS_PORT); 336 } 337 return null; 338 } 339 340 /** 341 * Returns the administration connector URL for the given server properties. 342 * It returns NULL if according to the server properties no administration 343 * connector URL can be generated. 344 * @param serverProperties the server properties to be used to generate 345 * the administration connector URL. 346 * @return the administration connector URL for the given server properties. 347 */ 348 private String getAdminConnectorUrl( 349 Map<ServerProperty,Object> serverProperties) 350 { 351 boolean portDefined; 352 if (isPropertyEnabled(serverProperties, ServerProperty.ADMIN_ENABLED)) 353 { 354 Object v = serverProperties.get(ServerProperty.ADMIN_PORT); 355 portDefined = v != null; 356 } 357 else 358 { 359 portDefined = false; 360 } 361 362 if (portDefined) 363 { 364 return "ldaps://" + getHostNameForLdapUrl(serverProperties) + ":" 365 + serverProperties.get(ServerProperty.ADMIN_PORT); 366 } 367 return null; 368 } 369 370 private boolean isLdapEnabled(Map<ServerProperty, Object> serverProperties) 371 { 372 return isPropertyEnabled(serverProperties, ServerProperty.LDAP_ENABLED); 373 } 374 375 private boolean isLdapsEnabled(Map<ServerProperty, Object> serverProperties) 376 { 377 return isPropertyEnabled(serverProperties, ServerProperty.LDAPS_ENABLED); 378 } 379 380 private boolean isStartTlsEnabled(Map<ServerProperty, Object> serverProperties) 381 { 382 return isPropertyEnabled(serverProperties, ServerProperty.STARTTLS_ENABLED); 383 } 384 385 private boolean isPropertyEnabled(Map<ServerProperty, Object> serverProperties, ServerProperty property) 386 { 387 Object v = serverProperties.get(property); 388 return v != null && "true".equalsIgnoreCase(v.toString()); 389 } 390 391 /** 392 * Returns the host name to be used to generate an LDAP URL based on the 393 * contents of the provided server properties. 394 * @param serverProperties the server properties. 395 * @return the host name to be used to generate an LDAP URL based on the 396 * contents of the provided server properties. 397 */ 398 private String getHostNameForLdapUrl( 399 Map<ServerProperty,Object> serverProperties) 400 { 401 String host = (String)serverProperties.get(ServerProperty.HOST_NAME); 402 return Utils.getHostNameForLdapUrl(host); 403 } 404 405 /** 406 * Returns whether the DN provided in the constructor is a Global 407 * Administrator DN or not. 408 * @return <CODE>true</CODE> if the DN provided in the constructor is a Global 409 * Administrator DN and <CODE>false</CODE> otherwise. 410 */ 411 private boolean isAdministratorDn() 412 { 413 try 414 { 415 LdapName theDn = new LdapName(dn); 416 LdapName containerDn = 417 new LdapName(ADSContext.getAdministratorContainerDN()); 418 return theDn.startsWith(containerDn); 419 } 420 catch (Throwable t) 421 { 422 logger.warn(LocalizableMessage.raw("Error parsing authentication DNs.", t)); 423 } 424 return false; 425 } 426 427 /** 428 * Returns the list of LDAP URLs that can be used to connect to the server. 429 * They are ordered so that the first URL is the preferred URL to be used. 430 * @return the list of LDAP URLs that can be used to connect to the server. 431 * They are ordered so that the first URL is the preferred URL to be used. 432 */ 433 private LinkedHashSet<PreferredConnection> getLDAPURLsByPreference() 434 { 435 LinkedHashSet<PreferredConnection> ldapUrls = new LinkedHashSet<>(); 436 437 String adminConnectorUrl = getAdminConnectorUrl(serverProperties); 438 String ldapsUrl = getLdapsUrl(serverProperties); 439 String startTLSUrl = getStartTlsLdapUrl(serverProperties); 440 String ldapUrl = getLdapUrl(serverProperties); 441 442 // Check the preferred connections passed in the constructor. 443 for (PreferredConnection connection : preferredLDAPURLs) 444 { 445 String url = connection.getLDAPURL(); 446 if (url.equalsIgnoreCase(adminConnectorUrl)) 447 { 448 ldapUrls.add(connection); 449 } 450 else if (url.equalsIgnoreCase(ldapsUrl) && 451 connection.getType() == PreferredConnection.Type.LDAPS) 452 { 453 ldapUrls.add(connection); 454 } 455 else if (url.equalsIgnoreCase(startTLSUrl) && 456 connection.getType() == PreferredConnection.Type.START_TLS) 457 { 458 ldapUrls.add(connection); 459 } 460 else if (url.equalsIgnoreCase(ldapUrl) && 461 connection.getType() == PreferredConnection.Type.LDAP) 462 { 463 ldapUrls.add(connection); 464 } 465 } 466 467 if (adminConnectorUrl != null) 468 { 469 ldapUrls.add( 470 new PreferredConnection(adminConnectorUrl, 471 PreferredConnection.Type.LDAPS)); 472 } 473 if (ldapsUrl != null) 474 { 475 ldapUrls.add( 476 new PreferredConnection(ldapsUrl, PreferredConnection.Type.LDAPS)); 477 } 478 if (startTLSUrl != null) 479 { 480 ldapUrls.add(new PreferredConnection(startTLSUrl, 481 PreferredConnection.Type.START_TLS)); 482 } 483 if (ldapUrl != null) 484 { 485 ldapUrls.add(new PreferredConnection(ldapUrl, 486 PreferredConnection.Type.LDAP)); 487 } 488 return ldapUrls; 489 } 490}