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.server.admin.client.ldap; 028 029import java.util.Collection; 030import java.util.Hashtable; 031import java.util.LinkedList; 032import java.util.List; 033 034import javax.naming.Context; 035import javax.naming.NameNotFoundException; 036import javax.naming.NamingEnumeration; 037import javax.naming.NamingException; 038import javax.naming.directory.Attribute; 039import javax.naming.directory.Attributes; 040import javax.naming.directory.DirContext; 041import javax.naming.directory.ModificationItem; 042import javax.naming.directory.SearchControls; 043import javax.naming.directory.SearchResult; 044import javax.naming.ldap.InitialLdapContext; 045import javax.naming.ldap.LdapName; 046import javax.naming.ldap.Rdn; 047 048import org.opends.admin.ads.util.BlindTrustManager; 049import org.opends.admin.ads.util.TrustedSocketFactory; 050import org.opends.server.admin.client.AuthenticationException; 051import org.opends.server.admin.client.AuthenticationNotSupportedException; 052import org.opends.server.admin.client.CommunicationException; 053import org.opends.server.schema.SchemaConstants; 054 055import static com.forgerock.opendj.cli.Utils.*; 056 057/** 058 * An LDAP connection adaptor which maps LDAP requests onto an 059 * underlying JNDI connection context. 060 */ 061public final class JNDIDirContextAdaptor extends LDAPConnection { 062 063 /** 064 * Adapts the provided JNDI <code>DirContext</code>. 065 * 066 * @param dirContext 067 * The JNDI connection. 068 * @return Returns a new JNDI connection adaptor. 069 */ 070 public static JNDIDirContextAdaptor adapt(DirContext dirContext) { 071 return new JNDIDirContextAdaptor(dirContext); 072 } 073 074 075 076 /** 077 * Creates a new JNDI connection adaptor by performing a simple bind 078 * operation to the specified LDAP server. 079 * 080 * @param host 081 * The host. 082 * @param port 083 * The port. 084 * @param name 085 * The LDAP bind DN. 086 * @param password 087 * The LDAP bind password. 088 * @return Returns a new JNDI connection adaptor. 089 * @throws CommunicationException 090 * If the client cannot contact the server due to an 091 * underlying communication problem. 092 * @throws AuthenticationNotSupportedException 093 * If the server does not support simple authentication. 094 * @throws AuthenticationException 095 * If authentication failed for some reason, usually due 096 * to invalid credentials. 097 */ 098 public static JNDIDirContextAdaptor simpleBind(String host, int port, 099 String name, String password) throws CommunicationException, 100 AuthenticationNotSupportedException, AuthenticationException { 101 Hashtable<String, Object> env = new Hashtable<>(); 102 env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); 103 String hostname = getHostNameForLdapUrl(host); 104 env.put(Context.PROVIDER_URL, "ldap://" + hostname + ":" + port); 105 env.put(Context.SECURITY_PRINCIPAL, name); 106 env.put(Context.SECURITY_CREDENTIALS, password); 107 return createJNDIDirContextAdaptor(env); 108 } 109 110 /** 111 * Creates a new JNDI connection adaptor by performing a simple bind 112 * operation to the specified LDAP server. 113 * 114 * @param host 115 * The host. 116 * @param port 117 * The port. 118 * @param name 119 * The LDAP bind DN. 120 * @param password 121 * The LDAP bind password. 122 * @return Returns a new JNDI connection adaptor. 123 * @throws CommunicationException 124 * If the client cannot contact the server due to an 125 * underlying communication problem. 126 * @throws AuthenticationNotSupportedException 127 * If the server does not support simple authentication. 128 * @throws AuthenticationException 129 * If authentication failed for some reason, usually due 130 * to invalid credentials. 131 */ 132 public static JNDIDirContextAdaptor simpleSSLBind(String host, int port, 133 String name, String password) throws CommunicationException, 134 AuthenticationNotSupportedException, AuthenticationException { 135 Hashtable<String, Object> env = new Hashtable<>(); 136 env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); 137 String hostname = getHostNameForLdapUrl(host); 138 env.put(Context.PROVIDER_URL, "ldaps://" + hostname + ":" + port); 139 env.put(Context.SECURITY_PRINCIPAL, name); 140 env.put(Context.SECURITY_CREDENTIALS, password); 141 env.put(Context.SECURITY_AUTHENTICATION, "simple"); 142 // Specify SSL 143 env.put(Context.SECURITY_PROTOCOL, "ssl"); 144 env.put("java.naming.ldap.factory.socket", 145 org.opends.admin.ads.util.TrustedSocketFactory.class.getName()); 146 TrustedSocketFactory.setCurrentThreadTrustManager(new BlindTrustManager(), null); 147 return createJNDIDirContextAdaptor(env); 148 } 149 150 private static JNDIDirContextAdaptor createJNDIDirContextAdaptor(Hashtable<String, Object> env) 151 throws CommunicationException, AuthenticationException, AuthenticationNotSupportedException 152 { 153 DirContext ctx; 154 try { 155 ctx = new InitialLdapContext(env, null); 156 } catch (javax.naming.AuthenticationException e) { 157 throw new AuthenticationException(e); 158 } catch (javax.naming.AuthenticationNotSupportedException e) { 159 throw new AuthenticationNotSupportedException(e); 160 } catch (NamingException e) { 161 // Assume some kind of communication problem. 162 throw new CommunicationException(e); 163 } 164 return new JNDIDirContextAdaptor(ctx); 165 } 166 167 168 /** The JNDI connection context. */ 169 private final DirContext dirContext; 170 171 /** 172 * Create a new JNDI connection adaptor using the provider JNDI 173 * DirContext. 174 */ 175 private JNDIDirContextAdaptor(DirContext dirContext) { 176 this.dirContext = dirContext; 177 } 178 179 /** {@inheritDoc} */ 180 @Override 181 public void createEntry(LdapName dn, Attributes attributes) 182 throws NamingException { 183 dirContext.createSubcontext(dn, attributes).close(); 184 } 185 186 /** {@inheritDoc} */ 187 @Override 188 public void deleteSubtree(LdapName dn) throws NamingException { 189 // Delete the children first. 190 for (LdapName child : listEntries(dn, null)) { 191 deleteSubtree(child); 192 } 193 194 // Delete the named entry. 195 dirContext.destroySubcontext(dn); 196 } 197 198 199 200 /** {@inheritDoc} */ 201 @Override 202 public boolean entryExists(LdapName dn) throws NamingException { 203 boolean entryExists = false; 204 String filter = "(objectClass=*)"; 205 SearchControls controls = new SearchControls(); 206 controls.setSearchScope(SearchControls.OBJECT_SCOPE); 207 controls.setReturningAttributes(new String[] { SchemaConstants.NO_ATTRIBUTES }); 208 try { 209 NamingEnumeration<SearchResult> results = dirContext.search(dn, filter, controls); 210 try 211 { 212 while (results.hasMore()) { 213 // To avoid having a systematic abandon in the server. 214 results.next(); 215 entryExists = true; 216 } 217 } 218 finally 219 { 220 results.close(); 221 } 222 } catch (NameNotFoundException e) { 223 // Fall through - entry not found. 224 } 225 return entryExists; 226 } 227 228 229 230 /** {@inheritDoc} */ 231 @Override 232 public Collection<LdapName> listEntries(LdapName dn, String filter) 233 throws NamingException { 234 if (filter == null) { 235 filter = "(objectClass=*)"; 236 } 237 238 SearchControls controls = new SearchControls(); 239 controls.setSearchScope(SearchControls.ONELEVEL_SCOPE); 240 241 List<LdapName> children = new LinkedList<>(); 242 NamingEnumeration<SearchResult> results = dirContext.search(dn, filter, controls); 243 try 244 { 245 while (results.hasMore()) { 246 SearchResult sr = results.next(); 247 LdapName child = new LdapName(dn.getRdns()); 248 child.add(new Rdn(sr.getName())); 249 children.add(child); 250 } 251 } 252 finally 253 { 254 results.close(); 255 } 256 257 return children; 258 } 259 260 261 262 /** {@inheritDoc} */ 263 @Override 264 public void modifyEntry(LdapName dn, Attributes mods) throws NamingException { 265 ModificationItem[] modList = new ModificationItem[mods.size()]; 266 NamingEnumeration<? extends Attribute> ne = mods.getAll(); 267 for (int i = 0; ne.hasMore(); i++) { 268 ModificationItem modItem = new ModificationItem( 269 DirContext.REPLACE_ATTRIBUTE, ne.next()); 270 modList[i] = modItem; 271 } 272 dirContext.modifyAttributes(dn, modList); 273 } 274 275 276 277 /** {@inheritDoc} */ 278 @Override 279 public Attributes readEntry(LdapName dn, Collection<String> attrIds) 280 throws NamingException { 281 String[] attrIdList = attrIds.toArray(new String[attrIds.size()]); 282 return dirContext.getAttributes(dn, attrIdList); 283 } 284 285 286 287 /** {@inheritDoc} */ 288 @Override 289 public void unbind() { 290 try { 291 dirContext.close(); 292 } catch (NamingException e) { 293 // nothing to do 294 } 295 } 296}