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