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}