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 2007-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2013-2016 ForgeRock AS.
016 */
017package org.opends.admin.ads;
018
019import static org.opends.admin.ads.util.ConnectionUtils.*;
020import static org.opends.quicksetup.util.Utils.*;
021
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.LinkedHashSet;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029
030import javax.naming.NameAlreadyBoundException;
031import javax.naming.NameNotFoundException;
032import javax.naming.NamingEnumeration;
033import javax.naming.NamingException;
034import javax.naming.directory.Attribute;
035import javax.naming.directory.Attributes;
036import javax.naming.directory.BasicAttribute;
037import javax.naming.directory.BasicAttributes;
038import javax.naming.directory.SearchControls;
039import javax.naming.directory.SearchResult;
040import javax.naming.ldap.InitialLdapContext;
041import javax.naming.ldap.LdapName;
042import javax.naming.ldap.Rdn;
043
044import org.forgerock.i18n.LocalizableMessage;
045import org.forgerock.i18n.slf4j.LocalizedLogger;
046import org.opends.admin.ads.util.ConnectionUtils;
047import org.opends.quicksetup.Constants;
048import org.opends.server.config.ConfigConstants;
049import org.opends.server.types.HostPort;
050
051/** The object of this class represent an OpenDS server. */
052public class ServerDescriptor
053{
054  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
055  private static final String TRUSTSTORE_DN = "cn=ads-truststore";
056
057  private final Map<ADSContext.ServerProperty, Object> adsProperties = new HashMap<>();
058  private final Set<ReplicaDescriptor> replicas = new HashSet<>();
059  private final Map<ServerProperty, Object> serverProperties = new HashMap<>();
060  private TopologyCacheException lastException;
061
062  /**
063   * Enumeration containing the different server properties that we can keep in
064   * the ServerProperty object.
065   */
066  public enum ServerProperty
067  {
068    /** The associated value is a String. */
069    HOST_NAME,
070    /** The associated value is an ArrayList of Integer. */
071    LDAP_PORT,
072    /** The associated value is an ArrayList of Integer. */
073    LDAPS_PORT,
074    /** The associated value is an Integer. */
075    ADMIN_PORT,
076    /** The associated value is an ArrayList of Boolean. */
077    LDAP_ENABLED,
078    /** The associated value is an ArrayList of Boolean. */
079    LDAPS_ENABLED,
080    /** The associated value is an ArrayList of Boolean. */
081    ADMIN_ENABLED,
082    /** The associated value is an ArrayList of Boolean. */
083    STARTTLS_ENABLED,
084    /** The associated value is an ArrayList of Integer. */
085    JMX_PORT,
086    /** The associated value is an ArrayList of Integer. */
087    JMXS_PORT,
088    /** The associated value is an ArrayList of Boolean. */
089    JMX_ENABLED,
090    /** The associated value is an ArrayList of Boolean. */
091    JMXS_ENABLED,
092    /** The associated value is an Integer. */
093    REPLICATION_SERVER_PORT,
094    /** The associated value is a Boolean. */
095    IS_REPLICATION_SERVER,
096    /** The associated value is a Boolean. */
097    IS_REPLICATION_ENABLED,
098    /** The associated value is a Boolean. */
099    IS_REPLICATION_SECURE,
100    /** List of servers specified in the Replication Server configuration. This is a Set of String. */
101    EXTERNAL_REPLICATION_SERVERS,
102    /** The associated value is an Integer. */
103    REPLICATION_SERVER_ID,
104    /**
105     * The instance key-pair public-key certificate. The associated value is a
106     * byte[] (ds-cfg-public-key-certificate;binary).
107     */
108    INSTANCE_PUBLIC_KEY_CERTIFICATE,
109    /** The schema generation ID. */
110    SCHEMA_GENERATION_ID
111  }
112
113  /** Default constructor. */
114  protected ServerDescriptor()
115  {
116  }
117
118  /**
119   * Returns the replicas contained on the server.
120   * @return the replicas contained on the server.
121   */
122  public Set<ReplicaDescriptor> getReplicas()
123  {
124    return new HashSet<>(replicas);
125  }
126
127  /**
128   * Sets the replicas contained on the server.
129   * @param replicas the replicas contained on the server.
130   */
131  public void setReplicas(Set<ReplicaDescriptor> replicas)
132  {
133    this.replicas.clear();
134    this.replicas.addAll(replicas);
135  }
136
137  /**
138   * Returns a Map containing the ADS properties of the server.
139   * @return a Map containing the ADS properties of the server.
140   */
141  public Map<ADSContext.ServerProperty, Object> getAdsProperties()
142  {
143    return adsProperties;
144  }
145
146  /**
147   * Returns a Map containing the properties of the server.
148   * @return a Map containing the properties of the server.
149   */
150  public Map<ServerProperty, Object> getServerProperties()
151  {
152    return serverProperties;
153  }
154
155  /**
156   * Tells whether this server is registered in the ADS or not.
157   * @return <CODE>true</CODE> if the server is registered in the ADS and
158   * <CODE>false</CODE> otherwise.
159   */
160  public boolean isRegistered()
161  {
162    return !adsProperties.isEmpty();
163  }
164
165  /**
166   * Tells whether this server is a replication server or not.
167   * @return <CODE>true</CODE> if the server is a replication server and
168   * <CODE>false</CODE> otherwise.
169   */
170  public boolean isReplicationServer()
171  {
172    return Boolean.TRUE.equals(
173        serverProperties.get(ServerProperty.IS_REPLICATION_SERVER));
174  }
175
176  /**
177   * Tells whether replication is enabled on this server or not.
178   * @return <CODE>true</CODE> if replication is enabled and
179   * <CODE>false</CODE> otherwise.
180   */
181  public boolean isReplicationEnabled()
182  {
183    return Boolean.TRUE.equals(
184        serverProperties.get(ServerProperty.IS_REPLICATION_ENABLED));
185  }
186
187  /**
188   * Returns the String representation of this replication server based
189   * on the information we have ("hostname":"replication port") and
190   * <CODE>null</CODE> if this is not a replication server.
191   * @return the String representation of this replication server based
192   * on the information we have ("hostname":"replication port") and
193   * <CODE>null</CODE> if this is not a replication server.
194   */
195  public String getReplicationServerHostPort()
196  {
197    if (isReplicationServer())
198    {
199      return getReplicationServer(getHostName(), getReplicationServerPort());
200    }
201    return null;
202  }
203
204  /**
205   * Returns the replication server ID of this server and -1 if this is not a
206   * replications server.
207   * @return the replication server ID of this server and -1 if this is not a
208   * replications server.
209   */
210  public int getReplicationServerId()
211  {
212    if (isReplicationServer())
213    {
214      return (Integer) serverProperties.get(ServerProperty.REPLICATION_SERVER_ID);
215    }
216    return -1;
217  }
218
219  /**
220   * Returns the replication port of this server and -1 if this is not a
221   * replications server.
222   * @return the replication port of this server and -1 if this is not a
223   * replications server.
224   */
225  public int getReplicationServerPort()
226  {
227    if (isReplicationServer())
228    {
229      return (Integer) serverProperties.get(
230          ServerProperty.REPLICATION_SERVER_PORT);
231    }
232    return -1;
233  }
234
235  /**
236   * Returns whether the communication with the replication port on the server
237   * is encrypted or not.
238   * @return <CODE>true</CODE> if the communication with the replication port on
239   * the server is encrypted and <CODE>false</CODE> otherwise.
240   */
241  public boolean isReplicationSecure()
242  {
243    return isReplicationServer()
244        && Boolean.TRUE.equals(serverProperties.get(ServerProperty.IS_REPLICATION_SECURE));
245  }
246
247  /**
248   * Sets the ADS properties of the server.
249   * @param adsProperties a Map containing the ADS properties of the server.
250   */
251  public void setAdsProperties(
252      Map<ADSContext.ServerProperty, Object> adsProperties)
253  {
254    this.adsProperties.clear();
255    this.adsProperties.putAll(adsProperties);
256  }
257
258  /**
259   * Returns the host name of the server.
260   * @return the host name of the server.
261   */
262  public String getHostName()
263  {
264    String host = (String)serverProperties.get(ServerProperty.HOST_NAME);
265    if (host != null)
266    {
267      return host;
268    }
269    return (String) adsProperties.get(ADSContext.ServerProperty.HOST_NAME);
270  }
271
272  /**
273   * Returns the URL to access this server using LDAP.  Returns
274   * <CODE>null</CODE> if the server is not configured to listen on an LDAP
275   * port.
276   * @return the URL to access this server using LDAP.
277   */
278  public String getLDAPURL()
279  {
280    return getLDAPUrl0(ServerProperty.LDAP_ENABLED, ServerProperty.LDAP_PORT, false);
281  }
282
283  /**
284   * Returns the URL to access this server using LDAPS.  Returns
285   * <CODE>null</CODE> if the server is not configured to listen on an LDAPS
286   * port.
287   * @return the URL to access this server using LDAP.
288   */
289  public String getLDAPsURL()
290  {
291    return getLDAPUrl0(ServerProperty.LDAPS_ENABLED, ServerProperty.LDAPS_PORT, true);
292  }
293
294  private String getLDAPUrl0(ServerProperty enabledProp, ServerProperty portProp, boolean useSSL)
295  {
296    int port = getPort(enabledProp, portProp);
297    if (port != -1)
298    {
299      String host = getHostName();
300      return getLDAPUrl(host, port, useSSL);
301    }
302    return null;
303  }
304
305  private int getPort(ServerProperty enabledProp, ServerProperty portProp)
306  {
307    if (!serverProperties.isEmpty())
308    {
309      return getPort(enabledProp, portProp, -1);
310    }
311    return -1;
312  }
313
314  /**
315   * Returns the URL to access this server using the administration connector.
316   * Returns <CODE>null</CODE> if the server cannot get the administration
317   * connector.
318   * @return the URL to access this server using the administration connector.
319   */
320  public String getAdminConnectorURL()
321  {
322    return getLDAPUrl0(ServerProperty.ADMIN_ENABLED, ServerProperty.ADMIN_PORT, true);
323  }
324
325  /**
326   * Returns the list of enabled administration ports.
327   * @return the list of enabled administration ports.
328   */
329  public List<Integer> getEnabledAdministrationPorts()
330  {
331    List<Integer> ports = new ArrayList<>(1);
332    ArrayList<?> s = (ArrayList<?>)serverProperties.get(ServerProperty.ADMIN_ENABLED);
333    ArrayList<?> p = (ArrayList<?>)serverProperties.get(ServerProperty.ADMIN_PORT);
334    if (s != null)
335    {
336      for (int i=0; i<s.size(); i++)
337      {
338        if (Boolean.TRUE.equals(s.get(i)))
339        {
340          ports.add((Integer)p.get(i));
341        }
342      }
343    }
344    return ports;
345  }
346
347  /**
348   * Returns a String of type host-name:port-number for the server.  If
349   * the provided securePreferred is set to true the port that will be used
350   * will be the administration connector port.
351   * @param securePreferred whether to try to use the secure port as part
352   * of the returning String or not.
353   * @return a String of type host-name:port-number for the server.
354   */
355  public HostPort getHostPort(boolean securePreferred)
356  {
357    int port = -1;
358
359    if (!serverProperties.isEmpty())
360    {
361      port = getPort(ServerProperty.LDAP_ENABLED, ServerProperty.LDAP_PORT, port);
362      if (securePreferred)
363      {
364        port = getPort(ServerProperty.ADMIN_ENABLED, ServerProperty.ADMIN_PORT, port);
365      }
366    }
367    else
368    {
369      ArrayList<ADSContext.ServerProperty> enabledAttrs = new ArrayList<>();
370
371      if (securePreferred)
372      {
373        enabledAttrs.add(ADSContext.ServerProperty.ADMIN_ENABLED);
374        enabledAttrs.add(ADSContext.ServerProperty.LDAPS_ENABLED);
375        enabledAttrs.add(ADSContext.ServerProperty.LDAP_ENABLED);
376      }
377      else
378      {
379        enabledAttrs.add(ADSContext.ServerProperty.LDAP_ENABLED);
380        enabledAttrs.add(ADSContext.ServerProperty.ADMIN_ENABLED);
381        enabledAttrs.add(ADSContext.ServerProperty.LDAPS_ENABLED);
382      }
383
384      for (ADSContext.ServerProperty prop : enabledAttrs)
385      {
386        Object v = adsProperties.get(prop);
387        if (v != null && "true".equalsIgnoreCase(String.valueOf(v)))
388        {
389          ADSContext.ServerProperty portProp = getPortProperty(prop);
390          Object p = adsProperties.get(portProp);
391          if (p != null)
392          {
393            try
394            {
395              port = Integer.parseInt(String.valueOf(p));
396            }
397            catch (Throwable t)
398            {
399              logger.warn(LocalizableMessage.raw("Error calculating host port: "+t+" in "+
400                  adsProperties, t));
401            }
402            break;
403          }
404          else
405          {
406            logger.warn(LocalizableMessage.raw("Value for "+portProp+" is null in "+
407                adsProperties));
408          }
409        }
410      }
411    }
412    return new HostPort(getHostName(), port);
413  }
414
415  private ADSContext.ServerProperty getPortProperty(ADSContext.ServerProperty prop)
416  {
417    if (prop == ADSContext.ServerProperty.ADMIN_ENABLED)
418    {
419      return ADSContext.ServerProperty.ADMIN_PORT;
420    }
421    else if (prop == ADSContext.ServerProperty.LDAPS_ENABLED)
422    {
423      return ADSContext.ServerProperty.LDAPS_PORT;
424    }
425    else if (prop == ADSContext.ServerProperty.LDAP_ENABLED)
426    {
427      return ADSContext.ServerProperty.LDAP_PORT;
428    }
429    else
430    {
431      throw new IllegalStateException("Unexpected prop: "+prop);
432    }
433  }
434
435  private int getPort(ServerProperty enabledProp, ServerProperty portProp, int defaultValue)
436  {
437    List<?> s = (List<?>) serverProperties.get(enabledProp);
438    if (s != null)
439    {
440      List<?> p = (List<?>) serverProperties.get(portProp);
441      for (int i=0; i<s.size(); i++)
442      {
443        if (Boolean.TRUE.equals(s.get(i)))
444        {
445          return (Integer) p.get(i);
446        }
447      }
448    }
449    return defaultValue;
450  }
451
452  /**
453   * Returns an Id that is unique for this server.
454   * @return an Id that is unique for this server.
455   */
456  public String getId()
457  {
458    StringBuilder buf = new StringBuilder();
459    if (!serverProperties.isEmpty())
460    {
461      buf.append(serverProperties.get(ServerProperty.HOST_NAME));
462      ServerProperty [] props =
463      {
464          ServerProperty.LDAP_PORT, ServerProperty.LDAPS_PORT,
465          ServerProperty.ADMIN_PORT,
466          ServerProperty.LDAP_ENABLED, ServerProperty.LDAPS_ENABLED,
467          ServerProperty.ADMIN_ENABLED
468      };
469      for (ServerProperty prop : props) {
470        ArrayList<?> s = (ArrayList<?>) serverProperties.get(prop);
471        for (Object o : s) {
472          buf.append(":").append(o);
473        }
474      }
475    }
476    else
477    {
478      ADSContext.ServerProperty[] props =
479      {
480          ADSContext.ServerProperty.HOST_NAME,
481          ADSContext.ServerProperty.LDAP_PORT,
482          ADSContext.ServerProperty.LDAPS_PORT,
483          ADSContext.ServerProperty.ADMIN_PORT,
484          ADSContext.ServerProperty.LDAP_ENABLED,
485          ADSContext.ServerProperty.LDAPS_ENABLED,
486          ADSContext.ServerProperty.ADMIN_ENABLED
487      };
488      for (int i=0; i<props.length; i++)
489      {
490        if (i != 0)
491        {
492          buf.append(":");
493        }
494        buf.append(adsProperties.get(props[i]));
495      }
496    }
497    return buf.toString();
498  }
499
500  /**
501   * Returns the instance-key public-key certificate retrieved from the
502   * truststore backend of the instance referenced through this descriptor.
503   *
504   * @return The public-key certificate of the instance.
505   */
506  public byte[] getInstancePublicKeyCertificate()
507  {
508    return (byte[]) serverProperties.get(ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE);
509  }
510
511  /**
512   * Returns the schema generation ID of the server.
513   * @return the schema generation ID of the server.
514   */
515  public String getSchemaReplicationID()
516  {
517    return (String)serverProperties.get(ServerProperty.SCHEMA_GENERATION_ID);
518  }
519
520  /**
521   * Returns the last exception that was encountered reading the configuration
522   * of the server.  Returns null if there was no problem loading the
523   * configuration of the server.
524   * @return the last exception that was encountered reading the configuration
525   * of the server.  Returns null if there was no problem loading the
526   * configuration of the server.
527   */
528  public TopologyCacheException getLastException()
529  {
530    return lastException;
531  }
532
533  /**
534   * Sets the last exception that occurred while reading the configuration of
535   * the server.
536   * @param lastException the last exception that occurred while reading the
537   * configuration of the server.
538   */
539  public void setLastException(TopologyCacheException lastException)
540  {
541    this.lastException = lastException;
542  }
543
544  /**
545   * This methods updates the ADS properties (the ones that were read from
546   * the ADS) with the contents of the server properties (the ones that were
547   * read directly from the server).
548   */
549  public void updateAdsPropertiesWithServerProperties()
550  {
551    adsProperties.put(ADSContext.ServerProperty.HOST_NAME, getHostName());
552    ServerProperty[][] sProps =
553    {
554        {ServerProperty.LDAP_ENABLED, ServerProperty.LDAP_PORT},
555        {ServerProperty.LDAPS_ENABLED, ServerProperty.LDAPS_PORT},
556        {ServerProperty.ADMIN_ENABLED, ServerProperty.ADMIN_PORT},
557        {ServerProperty.JMX_ENABLED, ServerProperty.JMX_PORT},
558        {ServerProperty.JMXS_ENABLED, ServerProperty.JMXS_PORT}
559    };
560    ADSContext.ServerProperty[][] adsProps =
561    {
562        {ADSContext.ServerProperty.LDAP_ENABLED,
563          ADSContext.ServerProperty.LDAP_PORT},
564        {ADSContext.ServerProperty.LDAPS_ENABLED,
565          ADSContext.ServerProperty.LDAPS_PORT},
566        {ADSContext.ServerProperty.ADMIN_ENABLED,
567          ADSContext.ServerProperty.ADMIN_PORT},
568        {ADSContext.ServerProperty.JMX_ENABLED,
569          ADSContext.ServerProperty.JMX_PORT},
570        {ADSContext.ServerProperty.JMXS_ENABLED,
571          ADSContext.ServerProperty.JMXS_PORT}
572    };
573
574    for (int i=0; i<sProps.length; i++)
575    {
576      ArrayList<?> s = (ArrayList<?>)serverProperties.get(sProps[i][0]);
577      ArrayList<?> p = (ArrayList<?>)serverProperties.get(sProps[i][1]);
578      if (s != null)
579      {
580        int port = getPort(s, p);
581        if (port == -1)
582        {
583          adsProperties.put(adsProps[i][0], "false");
584          if (!p.isEmpty())
585          {
586            port = (Integer)p.iterator().next();
587          }
588        }
589        else
590        {
591          adsProperties.put(adsProps[i][0], "true");
592        }
593        adsProperties.put(adsProps[i][1], String.valueOf(port));
594      }
595    }
596
597    ArrayList<?> array = (ArrayList<?>)serverProperties.get(
598        ServerProperty.STARTTLS_ENABLED);
599    boolean startTLSEnabled = false;
600    if (array != null && !array.isEmpty())
601    {
602      startTLSEnabled = Boolean.TRUE.equals(array.get(array.size() -1));
603    }
604    adsProperties.put(ADSContext.ServerProperty.STARTTLS_ENABLED, Boolean.toString(startTLSEnabled));
605    adsProperties.put(ADSContext.ServerProperty.ID, getHostPort(true).toString());
606    adsProperties.put(ADSContext.ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE,
607                      getInstancePublicKeyCertificate());
608  }
609
610  private int getPort(List<?> enabled, List<?> port)
611  {
612    for (int j = 0; j < enabled.size(); j++)
613    {
614      if (Boolean.TRUE.equals(enabled.get(j)))
615      {
616        return (Integer) port.get(j);
617      }
618    }
619    return -1;
620  }
621
622  /**
623   * Creates a ServerDescriptor object based on some ADS properties provided.
624   * @param adsProperties the ADS properties of the server.
625   * @return a ServerDescriptor object that corresponds to the provided ADS
626   * properties.
627   */
628  public static ServerDescriptor createStandalone(
629      Map<ADSContext.ServerProperty, Object> adsProperties)
630  {
631    ServerDescriptor desc = new ServerDescriptor();
632    desc.setAdsProperties(adsProperties);
633    return desc;
634  }
635
636  /**
637   * Creates a ServerDescriptor object based on the configuration that we read
638   * using the provided InitialLdapContext.
639   * @param ctx the InitialLdapContext that will be used to read the
640   * configuration of the server.
641   * @param filter the topology cache filter describing the information that
642   * must be retrieved.
643   * @return a ServerDescriptor object that corresponds to the read
644   * configuration.
645   * @throws NamingException if a problem occurred reading the server
646   * configuration.
647   */
648  public static ServerDescriptor createStandalone(InitialLdapContext ctx,
649      TopologyCacheFilter filter)
650  throws NamingException
651  {
652    ServerDescriptor desc = new ServerDescriptor();
653
654    updateLdapConfiguration(desc, ctx);
655    updateAdminConnectorConfiguration(desc, ctx);
656    updateJmxConfiguration(desc, ctx);
657    updateReplicas(desc, ctx, filter);
658    updateReplication(desc, ctx, filter);
659    updatePublicKeyCertificate(desc, ctx);
660    updateMiscellaneous(desc, ctx);
661
662    desc.serverProperties.put(ServerProperty.HOST_NAME,
663        ConnectionUtils.getHostName(ctx));
664
665    return desc;
666  }
667
668  private static void updateLdapConfiguration(ServerDescriptor desc, InitialLdapContext ctx)
669      throws NamingException
670  {
671    SearchControls ctls = new SearchControls();
672    ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
673    ctls.setReturningAttributes(
674        new String[] {
675            "ds-cfg-enabled",
676            "ds-cfg-listen-address",
677            "ds-cfg-listen-port",
678            "ds-cfg-use-ssl",
679            "ds-cfg-allow-start-tls",
680            "objectclass"
681        });
682    String filter = "(objectclass=ds-cfg-ldap-connection-handler)";
683
684    LdapName jndiName = new LdapName("cn=config");
685    NamingEnumeration<SearchResult> listeners =
686      ctx.search(jndiName, filter, ctls);
687
688    try
689    {
690      ArrayList<Integer> ldapPorts = new ArrayList<>();
691      ArrayList<Integer> ldapsPorts = new ArrayList<>();
692      ArrayList<Boolean> ldapEnabled = new ArrayList<>();
693      ArrayList<Boolean> ldapsEnabled = new ArrayList<>();
694      ArrayList<Boolean> startTLSEnabled = new ArrayList<>();
695
696      desc.serverProperties.put(ServerProperty.LDAP_PORT, ldapPorts);
697      desc.serverProperties.put(ServerProperty.LDAPS_PORT, ldapsPorts);
698      desc.serverProperties.put(ServerProperty.LDAP_ENABLED, ldapEnabled);
699      desc.serverProperties.put(ServerProperty.LDAPS_ENABLED, ldapsEnabled);
700      desc.serverProperties.put(ServerProperty.STARTTLS_ENABLED,
701          startTLSEnabled);
702
703      while(listeners.hasMore())
704      {
705        SearchResult sr = listeners.next();
706
707        String port = getFirstValue(sr, "ds-cfg-listen-port");
708
709        boolean isSecure = "true".equalsIgnoreCase(
710            getFirstValue(sr, "ds-cfg-use-ssl"));
711
712        boolean enabled = "true".equalsIgnoreCase(
713            getFirstValue(sr, "ds-cfg-enabled"));
714        final Integer portNumber = Integer.valueOf(port);
715        if (isSecure)
716        {
717          ldapsPorts.add(portNumber);
718          ldapsEnabled.add(enabled);
719        }
720        else
721        {
722          ldapPorts.add(portNumber);
723          ldapEnabled.add(enabled);
724          enabled = "true".equalsIgnoreCase(
725              getFirstValue(sr, "ds-cfg-allow-start-tls"));
726          startTLSEnabled.add(enabled);
727        }
728      }
729    }
730    finally
731    {
732      listeners.close();
733    }
734  }
735
736  private static void updateAdminConnectorConfiguration(ServerDescriptor desc, InitialLdapContext ctx)
737      throws NamingException
738  {
739    SearchControls ctls = new SearchControls();
740    ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
741    ctls.setReturningAttributes(
742        new String[] {
743            "ds-cfg-listen-port",
744            "objectclass"
745        });
746    String filter = "(objectclass=ds-cfg-administration-connector)";
747
748    LdapName jndiName = new LdapName("cn=config");
749    NamingEnumeration<SearchResult> listeners =
750      ctx.search(jndiName, filter, ctls);
751
752    try
753    {
754      Integer adminConnectorPort = null;
755
756      // we should have a single administration connector
757      while (listeners.hasMore()) {
758        SearchResult sr = listeners.next();
759        String port = getFirstValue(sr, "ds-cfg-listen-port");
760        adminConnectorPort = Integer.valueOf(port);
761      }
762
763      // Even if we have a single port, use an array to be consistent with
764      // other protocols.
765      ArrayList<Integer> adminPorts = new ArrayList<>();
766      ArrayList<Boolean> adminEnabled = new ArrayList<>();
767      if (adminConnectorPort != null)
768      {
769        adminPorts.add(adminConnectorPort);
770        adminEnabled.add(Boolean.TRUE);
771      }
772      desc.serverProperties.put(ServerProperty.ADMIN_PORT, adminPorts);
773      desc.serverProperties.put(ServerProperty.ADMIN_ENABLED, adminEnabled);
774    }
775    finally
776    {
777      listeners.close();
778    }
779  }
780
781  private static void updateJmxConfiguration(ServerDescriptor desc, InitialLdapContext ctx) throws NamingException
782  {
783    SearchControls ctls = new SearchControls();
784    ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
785    ctls.setReturningAttributes(
786        new String[] {
787            "ds-cfg-enabled",
788            "ds-cfg-listen-address",
789            "ds-cfg-listen-port",
790            "ds-cfg-use-ssl",
791            "objectclass"
792        });
793    String filter = "(objectclass=ds-cfg-jmx-connection-handler)";
794
795    LdapName jndiName = new LdapName("cn=config");
796    NamingEnumeration<SearchResult> listeners =
797      ctx.search(jndiName, filter, ctls);
798
799    ArrayList<Integer> jmxPorts = new ArrayList<>();
800    ArrayList<Integer> jmxsPorts = new ArrayList<>();
801    ArrayList<Boolean> jmxEnabled = new ArrayList<>();
802    ArrayList<Boolean> jmxsEnabled = new ArrayList<>();
803
804    desc.serverProperties.put(ServerProperty.JMX_PORT, jmxPorts);
805    desc.serverProperties.put(ServerProperty.JMXS_PORT, jmxsPorts);
806    desc.serverProperties.put(ServerProperty.JMX_ENABLED, jmxEnabled);
807    desc.serverProperties.put(ServerProperty.JMXS_ENABLED, jmxsEnabled);
808
809    try
810    {
811      while(listeners.hasMore())
812      {
813        SearchResult sr = listeners.next();
814
815        String port = getFirstValue(sr, "ds-cfg-listen-port");
816
817        boolean isSecure = "true".equalsIgnoreCase(
818            getFirstValue(sr, "ds-cfg-use-ssl"));
819
820        boolean enabled = "true".equalsIgnoreCase(
821            getFirstValue(sr, "ds-cfg-enabled"));
822        Integer portNumber = Integer.valueOf(port);
823        if (isSecure)
824        {
825          jmxsPorts.add(portNumber);
826          jmxsEnabled.add(enabled);
827        }
828        else
829        {
830          jmxPorts.add(portNumber);
831          jmxEnabled.add(enabled);
832        }
833      }
834    }
835    finally
836    {
837      listeners.close();
838    }
839  }
840
841  private static void updateReplicas(ServerDescriptor desc,
842      InitialLdapContext ctx, TopologyCacheFilter cacheFilter)
843  throws NamingException
844  {
845    if (!cacheFilter.searchBaseDNInformation())
846    {
847      return;
848    }
849    SearchControls ctls = new SearchControls();
850    ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
851    ctls.setReturningAttributes(
852        new String[] {
853            "ds-cfg-base-dn",
854            "ds-cfg-backend-id",
855            ConfigConstants.ATTR_OBJECTCLASS
856        });
857    String filter = "(objectclass=ds-cfg-backend)";
858
859    LdapName jndiName = new LdapName("cn=config");
860    NamingEnumeration<SearchResult> databases =
861      ctx.search(jndiName, filter, ctls);
862
863    try
864    {
865      while(databases.hasMore())
866      {
867        SearchResult sr = databases.next();
868
869        String id = getFirstValue(sr, "ds-cfg-backend-id");
870
871        if (!isConfigBackend(id) || isSchemaBackend(id))
872        {
873          Set<String> baseDns = getValues(sr, "ds-cfg-base-dn");
874
875          Set<String> entries;
876          if (cacheFilter.searchMonitoringInformation())
877          {
878            entries = getBaseDNEntryCount(ctx, id);
879          }
880          else
881          {
882            entries = new HashSet<>();
883          }
884
885          Set<ReplicaDescriptor> replicas = desc.getReplicas();
886          for (String baseDn : baseDns)
887          {
888            if (isAddReplica(cacheFilter, baseDn))
889            {
890              SuffixDescriptor suffix = new SuffixDescriptor();
891              suffix.setDN(baseDn);
892              ReplicaDescriptor replica = new ReplicaDescriptor();
893              replica.setServer(desc);
894              replica.setObjectClasses(getValues(sr, ConfigConstants.ATTR_OBJECTCLASS));
895              replica.setBackendName(id);
896              replicas.add(replica);
897              HashSet<ReplicaDescriptor> r = new HashSet<>();
898              r.add(replica);
899              suffix.setReplicas(r);
900              replica.setSuffix(suffix);
901              int nEntries = -1;
902              for (String s : entries)
903              {
904                int index = s.indexOf(" ");
905                if (index != -1)
906                {
907                  String dn = s.substring(index + 1);
908                  if (areDnsEqual(baseDn, dn))
909                  {
910                    try
911                    {
912                      nEntries = Integer.parseInt(s.substring(0, index));
913                    }
914                    catch (Throwable t)
915                    {
916                      /* Ignore */
917                    }
918                    break;
919                  }
920                }
921              }
922              replica.setEntries(nEntries);
923            }
924          }
925          desc.setReplicas(replicas);
926        }
927      }
928    }
929    finally
930    {
931      databases.close();
932    }
933  }
934
935  private static boolean isAddReplica(TopologyCacheFilter cacheFilter, String baseDn)
936  {
937    if (cacheFilter.searchAllBaseDNs())
938    {
939      return true;
940    }
941
942    for (String dn : cacheFilter.getBaseDNsToSearch())
943    {
944      if (areDnsEqual(dn, baseDn))
945      {
946        return true;
947      }
948    }
949    return false;
950  }
951
952  private static void updateReplication(ServerDescriptor desc,
953      InitialLdapContext ctx, TopologyCacheFilter cacheFilter)
954  throws NamingException
955  {
956    boolean replicationEnabled = false;
957    SearchControls ctls = new SearchControls();
958    ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
959    ctls.setReturningAttributes(
960        new String[] {
961            "ds-cfg-enabled"
962        });
963    String filter = "(objectclass=ds-cfg-synchronization-provider)";
964
965    LdapName jndiName = new LdapName(
966      "cn=Multimaster Synchronization,cn=Synchronization Providers,cn=config");
967    NamingEnumeration<SearchResult> syncProviders = null;
968
969    try
970    {
971      syncProviders = ctx.search(jndiName, filter, ctls);
972
973      while(syncProviders.hasMore())
974      {
975        SearchResult sr = syncProviders.next();
976
977        if ("true".equalsIgnoreCase(getFirstValue(sr,
978          "ds-cfg-enabled")))
979        {
980          replicationEnabled = true;
981        }
982      }
983    }
984    catch (NameNotFoundException nse)
985    {
986      /* ignore */
987    }
988    finally
989    {
990      if (syncProviders != null)
991      {
992        syncProviders.close();
993      }
994    }
995    desc.serverProperties.put(ServerProperty.IS_REPLICATION_ENABLED,
996        Boolean.valueOf(replicationEnabled));
997
998    Set<String> allReplicationServers = new LinkedHashSet<>();
999
1000    if (cacheFilter.searchBaseDNInformation())
1001    {
1002      ctls = new SearchControls();
1003      ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
1004      ctls.setReturningAttributes(
1005          new String[] {
1006              "ds-cfg-base-dn",
1007              "ds-cfg-replication-server",
1008              "ds-cfg-server-id"
1009          });
1010      filter = "(objectclass=ds-cfg-replication-domain)";
1011
1012      jndiName = new LdapName(
1013      "cn=Multimaster Synchronization,cn=Synchronization Providers,cn=config");
1014
1015      syncProviders = null;
1016      try
1017      {
1018        syncProviders = ctx.search(jndiName, filter, ctls);
1019
1020        while(syncProviders.hasMore())
1021        {
1022          SearchResult sr = syncProviders.next();
1023
1024          int id = Integer.parseInt(
1025              getFirstValue(sr, "ds-cfg-server-id"));
1026          Set<String> replicationServers = getValues(sr,
1027          "ds-cfg-replication-server");
1028          Set<String> dns = getValues(sr, "ds-cfg-base-dn");
1029          for (String dn : dns)
1030          {
1031            for (ReplicaDescriptor replica : desc.getReplicas())
1032            {
1033              if (areDnsEqual(replica.getSuffix().getDN(), dn))
1034              {
1035                replica.setReplicationId(id);
1036                // Keep the values of the replication servers in lower case
1037                // to make use of Sets as String simpler.
1038                LinkedHashSet<String> repServers = new LinkedHashSet<>();
1039                for (String s: replicationServers)
1040                {
1041                  repServers.add(s.toLowerCase());
1042                }
1043                replica.setReplicationServers(repServers);
1044                allReplicationServers.addAll(repServers);
1045              }
1046            }
1047          }
1048        }
1049      }
1050      catch (NameNotFoundException nse)
1051      {
1052        /* ignore */
1053      }
1054      finally
1055      {
1056        if (syncProviders != null)
1057        {
1058          syncProviders.close();
1059        }
1060      }
1061    }
1062
1063    ctls = new SearchControls();
1064    ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
1065    ctls.setReturningAttributes(
1066    new String[] {
1067      "ds-cfg-replication-port", "ds-cfg-replication-server",
1068      "ds-cfg-replication-server-id"
1069    });
1070    filter = "(objectclass=ds-cfg-replication-server)";
1071
1072    jndiName = new LdapName("cn=Multimaster "+
1073        "Synchronization,cn=Synchronization Providers,cn=config");
1074
1075    desc.serverProperties.put(ServerProperty.IS_REPLICATION_SERVER,
1076        Boolean.FALSE);
1077    NamingEnumeration<SearchResult> entries = null;
1078    try
1079    {
1080      entries = ctx.search(jndiName, filter, ctls);
1081
1082      while (entries.hasMore())
1083      {
1084        SearchResult sr = entries.next();
1085
1086        desc.serverProperties.put(ServerProperty.IS_REPLICATION_SERVER,
1087            Boolean.TRUE);
1088        String v = getFirstValue(sr, "ds-cfg-replication-port");
1089        desc.serverProperties.put(ServerProperty.REPLICATION_SERVER_PORT,
1090            Integer.parseInt(v));
1091        v = getFirstValue(sr, "ds-cfg-replication-server-id");
1092        desc.serverProperties.put(ServerProperty.REPLICATION_SERVER_ID,
1093            Integer.parseInt(v));
1094        Set<String> values = getValues(sr, "ds-cfg-replication-server");
1095        // Keep the values of the replication servers in lower case
1096        // to make use of Sets as String simpler.
1097        LinkedHashSet<String> repServers = new LinkedHashSet<>();
1098        for (String s: values)
1099        {
1100          repServers.add(s.toLowerCase());
1101        }
1102        allReplicationServers.addAll(repServers);
1103        desc.serverProperties.put(ServerProperty.EXTERNAL_REPLICATION_SERVERS,
1104            allReplicationServers);
1105      }
1106    }
1107    catch (NameNotFoundException nse)
1108    {
1109      /* ignore */
1110    }
1111    finally
1112    {
1113      if (entries != null)
1114      {
1115        entries.close();
1116      }
1117    }
1118
1119    boolean replicationSecure = false;
1120    if (replicationEnabled)
1121    {
1122      ctls = new SearchControls();
1123      ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
1124      ctls.setReturningAttributes(
1125      new String[] {"ds-cfg-ssl-encryption"});
1126      filter = "(objectclass=ds-cfg-crypto-manager)";
1127
1128      jndiName = new LdapName("cn=Crypto Manager,cn=config");
1129
1130      entries = ctx.search(jndiName, filter, ctls);
1131
1132      try
1133      {
1134        while (entries.hasMore())
1135        {
1136          SearchResult sr = entries.next();
1137
1138          String v = getFirstValue(sr, "ds-cfg-ssl-encryption");
1139          replicationSecure = "true".equalsIgnoreCase(v);
1140        }
1141      }
1142      finally
1143      {
1144        entries.close();
1145      }
1146    }
1147    desc.serverProperties.put(ServerProperty.IS_REPLICATION_SECURE,
1148        Boolean.valueOf(replicationSecure));
1149  }
1150
1151  /**
1152   Updates the instance key public-key certificate value of this context from
1153   the local truststore of the instance bound by this context. Any current
1154   value of the certificate is overwritten. The intent of this method is to
1155   retrieve the instance-key public-key certificate when this context is bound
1156   to an instance, and cache it for later use in registering the instance into
1157   ADS.
1158   @param desc The map to update with the instance key-pair public-key
1159   certificate.
1160   @param ctx The bound server instance.
1161   @throws NamingException if unable to retrieve certificate from bound
1162   instance.
1163   */
1164  private static void updatePublicKeyCertificate(ServerDescriptor desc, InitialLdapContext ctx) throws NamingException
1165  {
1166    /* TODO: this DN is declared in some core constants file. Create a constants
1167       file for the installer and import it into the core. */
1168    final String dnStr = "ds-cfg-key-id=ads-certificate,cn=ads-truststore";
1169    final LdapName dn = new LdapName(dnStr);
1170    for (int i = 0; i < 2 ; ++i) {
1171      /* If the entry does not exist in the instance's truststore backend, add
1172         it (which induces the CryptoManager to create the public-key
1173         certificate attribute), then repeat the search. */
1174      try {
1175        final SearchControls searchControls = new SearchControls();
1176        searchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
1177        final String attrIDs[] = { "ds-cfg-public-key-certificate;binary" };
1178        searchControls.setReturningAttributes(attrIDs);
1179        final SearchResult certEntry = ctx.search(dn,
1180                   "(objectclass=ds-cfg-instance-key)", searchControls).next();
1181        final Attribute certAttr = certEntry.getAttributes().get(attrIDs[0]);
1182        if (null != certAttr) {
1183          /* attribute ds-cfg-public-key-certificate is a MUST in the schema */
1184          desc.serverProperties.put(
1185                  ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE,
1186                  certAttr.get());
1187        }
1188        break;
1189      }
1190      catch (NameNotFoundException x) {
1191        if (0 == i) {
1192          // Poke CryptoManager to initialize truststore. Note the special attribute in the request.
1193          final Attributes attrs = new BasicAttributes();
1194          final Attribute oc = new BasicAttribute("objectclass");
1195          oc.add("top");
1196          oc.add("ds-cfg-self-signed-cert-request");
1197          attrs.put(oc);
1198          ctx.createSubcontext(dn, attrs).close();
1199        }
1200        else {
1201          throw x;
1202        }
1203      }
1204    }
1205  }
1206
1207  private static void updateMiscellaneous(ServerDescriptor desc, InitialLdapContext ctx) throws NamingException
1208  {
1209    SearchControls ctls = new SearchControls();
1210    ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
1211    ctls.setReturningAttributes(
1212        new String[] {
1213            "ds-sync-generation-id"
1214        });
1215    String filter = "(|(objectclass=*)(objectclass=ldapsubentry))";
1216
1217    LdapName jndiName = new LdapName("cn=schema");
1218    NamingEnumeration<SearchResult> listeners =
1219      ctx.search(jndiName, filter, ctls);
1220
1221    try
1222    {
1223      while(listeners.hasMore())
1224      {
1225        SearchResult sr = listeners.next();
1226
1227        desc.serverProperties.put(ServerProperty.SCHEMA_GENERATION_ID,
1228            getFirstValue(sr, "ds-sync-generation-id"));
1229      }
1230    }
1231    finally
1232    {
1233      listeners.close();
1234    }
1235  }
1236
1237  /**
1238   Seeds the bound instance's local ads-truststore with a set of instance
1239   key-pair public key certificates. The result is the instance will trust any
1240   instance possessing the private key corresponding to one of the public-key
1241   certificates. This trust is necessary at least to initialize replication,
1242   which uses the trusted certificate entries in the ads-truststore for server
1243   authentication.
1244   @param ctx The bound instance.
1245   @param keyEntryMap The set of valid (i.e., not tagged as compromised)
1246   instance key-pair public-key certificate entries in ADS represented as a map
1247   from keyID to public-key certificate (binary).
1248   @throws NamingException in case an error occurs while updating the instance's
1249   ads-truststore via LDAP.
1250   */
1251  public static void seedAdsTrustStore(
1252          InitialLdapContext ctx,
1253          Map<String, byte[]> keyEntryMap)
1254          throws NamingException
1255  {
1256    /* TODO: this DN is declared in some core constants file. Create a
1257       constants file for the installer and import it into the core. */
1258    final Attribute oc = new BasicAttribute("objectclass");
1259    oc.add("top");
1260    oc.add("ds-cfg-instance-key");
1261    for (Map.Entry<String, byte[]> keyEntry : keyEntryMap.entrySet()){
1262      final BasicAttributes keyAttrs = new BasicAttributes();
1263      keyAttrs.put(oc);
1264      final Attribute rdnAttr = new BasicAttribute(
1265              ADSContext.ServerProperty.INSTANCE_KEY_ID.getAttributeName(),
1266              keyEntry.getKey());
1267      keyAttrs.put(rdnAttr);
1268      keyAttrs.put(new BasicAttribute(
1269              ADSContext.ServerProperty.INSTANCE_PUBLIC_KEY_CERTIFICATE.
1270                      getAttributeName() + ";binary", keyEntry.getValue()));
1271      final LdapName keyDn = new LdapName(rdnAttr.getID() + "=" + Rdn.escapeValue(rdnAttr.get()) + "," + TRUSTSTORE_DN);
1272      try {
1273        ctx.createSubcontext(keyDn, keyAttrs).close();
1274      }
1275      catch(NameAlreadyBoundException x){
1276        ctx.destroySubcontext(keyDn);
1277        ctx.createSubcontext(keyDn, keyAttrs).close();
1278      }
1279    }
1280  }
1281
1282  /**
1283   * Returns the values of the ds-base-dn-entry count attributes for the given
1284   * backend monitor entry using the provided InitialLdapContext.
1285   * @param ctx the InitialLdapContext to use to update the configuration.
1286   * @param backendID the id of the backend.
1287   * @return the values of the ds-base-dn-entry count attribute.
1288   * @throws NamingException if there was an error.
1289   */
1290  private static Set<String> getBaseDNEntryCount(InitialLdapContext ctx,
1291      String backendID) throws NamingException
1292  {
1293    LinkedHashSet<String> v = new LinkedHashSet<>();
1294    SearchControls ctls = new SearchControls();
1295    ctls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
1296    ctls.setReturningAttributes(
1297        new String[] {
1298            "ds-base-dn-entry-count"
1299        });
1300    String filter = "(ds-backend-id="+backendID+")";
1301
1302    LdapName jndiName = new LdapName("cn=monitor");
1303    NamingEnumeration<SearchResult> listeners =
1304      ctx.search(jndiName, filter, ctls);
1305
1306    try
1307    {
1308      while(listeners.hasMore())
1309      {
1310        SearchResult sr = listeners.next();
1311
1312        v.addAll(getValues(sr, "ds-base-dn-entry-count"));
1313      }
1314    }
1315    finally
1316    {
1317      listeners.close();
1318    }
1319    return v;
1320  }
1321
1322  /**
1323   * An convenience method to know if the provided ID corresponds to a
1324   * configuration backend or not.
1325   * @param id the backend ID to analyze
1326   * @return <CODE>true</CODE> if the the id corresponds to a configuration
1327   * backend and <CODE>false</CODE> otherwise.
1328   */
1329  private static boolean isConfigBackend(String id)
1330  {
1331    return "tasks".equalsIgnoreCase(id) ||
1332    "schema".equalsIgnoreCase(id) ||
1333    "config".equalsIgnoreCase(id) ||
1334    "monitor".equalsIgnoreCase(id) ||
1335    "backup".equalsIgnoreCase(id) ||
1336    "ads-truststore".equalsIgnoreCase(id);
1337  }
1338
1339  /**
1340   * An convenience method to know if the provided ID corresponds to the schema
1341   * backend or not.
1342   * @param id the backend ID to analyze
1343   * @return <CODE>true</CODE> if the the id corresponds to the schema backend
1344   * and <CODE>false</CODE> otherwise.
1345   */
1346  private static boolean isSchemaBackend(String id)
1347  {
1348    return "schema".equalsIgnoreCase(id);
1349  }
1350
1351  /**
1352   * Returns the replication server normalized String for a given host name
1353   * and replication port.
1354   * @param hostName the host name.
1355   * @param replicationPort the replication port.
1356   * @return the replication server normalized String for a given host name
1357   * and replication port.
1358   */
1359  public static String getReplicationServer(String hostName, int replicationPort)
1360  {
1361    return HostPort.toString(hostName, replicationPort);
1362  }
1363
1364  /**
1365   * Returns a representation of a base DN for a set of servers.
1366   * @param baseDN the base DN.
1367   * @param servers the servers.
1368   * @return a representation of a base DN for a set of servers.
1369   */
1370  public static String getSuffixDisplay(String baseDN,
1371      Set<ServerDescriptor> servers)
1372  {
1373    StringBuilder sb = new StringBuilder();
1374    sb.append(baseDN);
1375    for (ServerDescriptor server : servers)
1376    {
1377      sb.append(Constants.LINE_SEPARATOR).append("    ");
1378      sb.append(server.getHostPort(true));
1379    }
1380    return sb.toString();
1381  }
1382
1383  /**
1384   * Tells whether the provided server descriptor represents the same server
1385   * as this object.
1386   * @param server the server to make the comparison.
1387   * @return whether the provided server descriptor represents the same server
1388   * as this object or not.
1389   */
1390  public boolean isSameServer(ServerDescriptor server)
1391  {
1392    return getId().equals(server.getId());
1393  }
1394}