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 2016 ForgeRock AS. 015 */ 016package org.opends.admin.ads.util; 017 018import static org.forgerock.opendj.config.client.ldap.LDAPManagementContext.*; 019import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*; 020import static org.forgerock.opendj.ldap.requests.Requests.*; 021import static org.forgerock.util.time.Duration.*; 022import static org.opends.admin.ads.util.ConnectionUtils.*; 023import static org.opends.admin.ads.util.PreferredConnection.Type.*; 024import static org.opends.messages.AdminToolMessages.*; 025 026import java.io.Closeable; 027import java.net.URI; 028import java.net.URISyntaxException; 029import java.security.GeneralSecurityException; 030import java.security.NoSuchAlgorithmException; 031import java.util.concurrent.TimeUnit; 032 033import javax.naming.NamingException; 034import javax.naming.NoPermissionException; 035import javax.naming.ldap.InitialLdapContext; 036import javax.net.ssl.KeyManager; 037import javax.net.ssl.SSLContext; 038import javax.net.ssl.TrustManager; 039 040import com.forgerock.opendj.cli.ConnectionFactoryProvider; 041import org.forgerock.opendj.config.LDAPProfile; 042import org.forgerock.opendj.ldap.Connection; 043import org.forgerock.opendj.ldap.LDAPConnectionFactory; 044import org.forgerock.opendj.ldap.LdapException; 045import org.forgerock.opendj.ldap.SSLContextBuilder; 046import org.forgerock.opendj.ldap.requests.SimpleBindRequest; 047import org.forgerock.opendj.server.config.client.RootCfgClient; 048import org.forgerock.util.Options; 049import org.opends.admin.ads.util.PreferredConnection.Type; 050import org.opends.server.types.HostPort; 051import org.opends.server.util.StaticUtils; 052 053/** 054 * Wraps a connection to a directory, either relying on JNDI or relying on OpenDJ Connection. 055 * <p> 056 * You can either: 057 * <ul> 058 * <li>call {@code getLdapContext()} method to obtain an {@code InitialLdapContext} for JNDI.</li> 059 * <li>or call the {@code getConnection()} method to obtain a {@code Connection} object.</li> 060 * </ul> 061 */ 062public class ConnectionWrapper implements Closeable 063{ 064 private final LDAPConnectionFactory connectionFactory; 065 private final Connection connection; 066 private final InitialLdapContext ldapContext; 067 private final HostPort hostPort; 068 private final int connectTimeout; 069 private final TrustManager trustManager; 070 private final KeyManager keyManager; 071 072 /** 073 * Creates a connection wrapper. 074 * 075 * @param ldapUrl 076 * the ldap URL containing the host name and port number to connect to 077 * @param connectionType 078 * the type of connection (LDAP, LDAPS, START_TLS) 079 * @param bindDn 080 * the bind DN 081 * @param bindPwd 082 * the bind password 083 * @param connectTimeout 084 * connect timeout to use for the connection 085 * @param trustManager 086 * trust manager to use for a secure connection 087 * @throws NamingException 088 * If an error occurs 089 */ 090 public ConnectionWrapper(String ldapUrl, Type connectionType, String bindDn, String bindPwd, int connectTimeout, 091 ApplicationTrustManager trustManager) throws NamingException 092 { 093 this(toHostPort(ldapUrl), connectionType, bindDn, bindPwd, connectTimeout, trustManager); 094 } 095 096 private static HostPort toHostPort(String ldapUrl) throws NamingException 097 { 098 try 099 { 100 URI uri = new URI(ldapUrl); 101 return new HostPort(uri.getHost(), uri.getPort()); 102 } 103 catch (URISyntaxException e) 104 { 105 throw new NamingException(e.getLocalizedMessage() + ". LDAP URL was: \"" + ldapUrl + "\""); 106 } 107 } 108 109 /** 110 * Creates a connection wrapper. 111 * 112 * @param hostPort 113 * the host name and port number to connect to 114 * @param connectionType 115 * the type of connection (LDAP, LDAPS, START_TLS) 116 * @param bindDn 117 * the bind DN 118 * @param bindPwd 119 * the bind password 120 * @param connectTimeout 121 * connect timeout to use for the connection 122 * @param trustManager 123 * trust manager to use for a secure connection 124 * @throws NamingException 125 * If an error occurs 126 */ 127 public ConnectionWrapper(HostPort hostPort, Type connectionType, String bindDn, String bindPwd, int connectTimeout, 128 TrustManager trustManager) throws NamingException 129 { 130 this(hostPort, connectionType, bindDn, bindPwd, connectTimeout, trustManager, null); 131 } 132 133 /** 134 * Creates a connection wrapper. 135 * 136 * @param hostPort 137 * the host name and port number to connect to 138 * @param connectionType 139 * the type of connection (LDAP, LDAPS, START_TLS) 140 * @param bindDn 141 * the bind DN 142 * @param bindPwd 143 * the bind password 144 * @param connectTimeout 145 * connect timeout to use for the connection 146 * @param trustManager 147 * trust manager to use for a secure connection 148 * @param keyManager 149 * key manager to use for a secure connection 150 * @throws NamingException 151 * If an error occurs 152 */ 153 public ConnectionWrapper(HostPort hostPort, PreferredConnection.Type connectionType, String bindDn, String bindPwd, 154 int connectTimeout, TrustManager trustManager, KeyManager keyManager) throws NamingException 155 { 156 this.hostPort = hostPort; 157 this.connectTimeout = connectTimeout; 158 this.trustManager = trustManager; 159 this.keyManager = keyManager; 160 161 final Options options = toOptions(connectionType, bindDn, bindPwd, connectTimeout, trustManager, keyManager); 162 ldapContext = createAdministrativeContext(options, bindDn, bindPwd); 163 connectionFactory = new LDAPConnectionFactory(hostPort.getHost(), hostPort.getPort(), options); 164 connection = buildConnection(); 165 } 166 167 private static Options toOptions(Type connectionType, String bindDn, String bindPwd, long connectTimeout, 168 TrustManager trustManager, KeyManager keyManager) throws NamingException 169 { 170 final boolean isStartTls = START_TLS.equals(connectionType); 171 final boolean isLdaps = LDAPS.equals(connectionType); 172 173 Options options = Options.defaultOptions() 174 .set(CONNECT_TIMEOUT, duration(connectTimeout, TimeUnit.MILLISECONDS)); 175 if (isLdaps || isStartTls) 176 { 177 try { 178 options.set(SSL_CONTEXT, getSSLContext(trustManager, keyManager)) 179 .set(SSL_USE_STARTTLS, isStartTls) 180 .set(SSL_ENABLED_PROTOCOLS, ConnectionFactoryProvider.getDefaultProtocols()); 181 } catch (NoSuchAlgorithmException e) { 182 throw new NamingException("Unable to perform SSL initialization:" + e.getMessage()); 183 } 184 } 185 SimpleBindRequest request = bindDn != null && bindPwd != null 186 ? newSimpleBindRequest(bindDn, bindPwd.toCharArray()) 187 : newSimpleBindRequest(); // anonymous bind 188 options.set(AUTHN_BIND_REQUEST, request); 189 return options; 190 } 191 192 private static SSLContext getSSLContext(TrustManager trustManager, KeyManager keyManager) throws NamingException 193 { 194 try 195 { 196 return new SSLContextBuilder() 197 .setTrustManager(trustManager != null ? trustManager : new BlindTrustManager()) 198 .setKeyManager(keyManager).getSSLContext(); 199 } 200 catch (GeneralSecurityException e) 201 { 202 throw new NamingException("Unable to perform SSL initialization:" + e.getMessage()); 203 } 204 } 205 206 private InitialLdapContext createAdministrativeContext(Options options, String bindDn, String bindPwd) 207 throws NamingException 208 { 209 final InitialLdapContext ctx = createAdministrativeContext0(options, bindDn, bindPwd); 210 if (!connectedAsAdministrativeUser(ctx)) 211 { 212 throw new NoPermissionException(ERR_NOT_ADMINISTRATIVE_USER.get().toString()); 213 } 214 return ctx; 215 } 216 217 private InitialLdapContext createAdministrativeContext0(Options options, String bindDn, String bindPwd) 218 throws NamingException 219 { 220 SSLContext sslContext = options.get(SSL_CONTEXT); 221 boolean useSSL = sslContext != null; 222 boolean useStartTLS = options.get(SSL_USE_STARTTLS); 223 final String ldapUrl = getLDAPUrl(getHostPort(), useSSL); 224 if (useSSL) 225 { 226 return createLdapsContext(ldapUrl, bindDn, bindPwd, connectTimeout, null, trustManager, keyManager); 227 } 228 else if (useStartTLS) 229 { 230 return createStartTLSContext(ldapUrl, bindDn, bindPwd, connectTimeout, null, trustManager, keyManager, null); 231 } 232 else 233 { 234 return createLdapContext(ldapUrl, bindDn, bindPwd, connectTimeout, null); 235 } 236 } 237 238 private Connection buildConnection() throws NamingException 239 { 240 try 241 { 242 return connectionFactory.getConnection(); 243 } 244 catch (LdapException e) 245 { 246 throw new NamingException("Unable to get a connection from connection factory:" + e.getMessage()); 247 } 248 } 249 250 /** 251 * Returns the connection. 252 * 253 * @return the connection 254 */ 255 public Connection getConnection() 256 { 257 return connection; 258 } 259 260 /** 261 * Returns the ldap context (JNDI). 262 * 263 * @return the ldap context 264 */ 265 public InitialLdapContext getLdapContext() 266 { 267 return ldapContext; 268 } 269 270 /** 271 * Returns the host name and port number of this connection. 272 * 273 * @return the hostPort of this connection 274 */ 275 public HostPort getHostPort() 276 { 277 return hostPort; 278 } 279 280 /** 281 * Returns the root configuration client by using the inrnal Connection. 282 * 283 * @return the root configuration client 284 */ 285 public RootCfgClient getRootConfiguration() 286 { 287 return newManagementContext(getConnection(), LDAPProfile.getInstance()).getRootConfiguration(); 288 } 289 290 @Override 291 public void close() 292 { 293 StaticUtils.close(connectionFactory, connection); 294 StaticUtils.close(ldapContext); 295 } 296}