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