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}