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