001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2013-2015 ForgeRock AS.
026 */
027
028package org.opends.quicksetup;
029
030import org.opends.quicksetup.util.Utils;
031
032import java.io.File;
033import java.io.FileReader;
034import java.io.BufferedReader;
035import java.io.IOException;
036import java.util.Set;
037import java.util.HashSet;
038
039/**
040 * Represents the contents of an OpenDS configuration file.
041 */
042public class Configuration {
043
044  private String contents;
045  private String lowerCaseContents;
046  private Installation install;
047  private File file;
048
049  /**
050   * Create a Configuration from a file.
051   * @param install of which this configuration is part
052   * @param file config.ldif file
053   */
054  public Configuration(Installation install, File file) {
055    if (install == null) {
056      throw new NullPointerException("config file cannot be null");
057    }
058    if (file == null) {
059      throw new NullPointerException("config file cannot be null");
060    } else if (
061            // Leave open the possibility that the file might be
062            // config.ldif.<svn rev>
063            !file.getName().startsWith("config.ldif")) {
064      throw new IllegalArgumentException("file must be a config.ldif file");
065    }
066    this.install = install;
067    this.file = file;
068  }
069
070  /**
071   * Returns the list of directory manager dns as they appear in the
072   * configuration file.
073   *
074   * @return the list of directory manager dns as they appear in the
075   *         configuration file.
076   * @throws IOException if there were problems reading the information from
077   * the configuration file.
078   */
079  public Set<String> getDirectoryManagerDns() throws IOException {
080    return getConfigurationValues("ds-cfg-alternate-bind-dn");
081  }
082
083  /**
084   * Provides the LDAP port as is specified in the config.ldif file.
085   *
086   * @return the LDAP port specified in the config.ldif file.
087   * @throws IOException if there were problems reading the information from
088   * the configuration file.
089   */
090  public int getPort() throws IOException {
091    return getLDAPPort("ds-cfg-listen-port");
092  }
093
094  /**
095   * Provides the administration port as is specified in the config.ldif file.
096   *
097   * @return the administration port specified in the config.ldif file.
098   * @throws IOException if there were problems reading the information from
099   * the configuration file.
100   */
101  public int getAdminConnectorPort() throws IOException
102  {
103    return getAdminConnectorPort("ds-cfg-listen-port");
104  }
105
106  /**
107   * Tells whether this server is configured as a replication server or not.
108   * @return <CODE>true</CODE> if the server is configured as a Replication
109   * Server and <CODE>false</CODE> otherwise.
110   * @throws IOException if there were problems reading the information from
111   * the configuration file.
112   */
113  public boolean isReplicationServer() throws IOException
114  {
115    return getReplicationPort() != -1;
116  }
117
118  /**
119   * Provides the Replication port as is specified in the config.ldif file.
120   * Returns -1 if this server is not a Replication Server.
121   *
122   * @return the Replication port specified in the config.ldif file.
123   * @throws IOException if there were problems reading the information from
124   * the configuration file.
125   */
126  public int getReplicationPort() throws IOException {
127    int port = -1;
128    String contents = getLowerCaseContents();
129    int index = contents.indexOf("cn=replication server");
130
131    if (index != -1) {
132      String attrWithPoints = "ds-cfg-replication-port:";
133      int index1 = contents.indexOf(attrWithPoints, index);
134      if (index1 != -1) {
135        int index2 =
136                contents.indexOf(Constants.LINE_SEPARATOR, index1);
137        if (index2 != -1) {
138          String sPort =
139                  contents.substring(attrWithPoints.length() +
140                          index1,
141                          index2).trim();
142          try {
143            port = Integer.parseInt(sPort);
144          } catch (NumberFormatException nfe) {
145            // do nothing;
146          }
147        }
148      }
149    }
150    return port;
151  }
152
153  /**
154   * Returns the list of paths where the logs files are located as they appear
155   * in the configuration file.
156   *
157   * @return the list of paths where the logs files are located as they appear
158   *         in the configuration file.
159   * @throws IOException if there were problems reading the information from
160   * the configuration file.
161   */
162  public Set<String> getLogPaths() throws IOException {
163    return getConfigurationValues("ds-cfg-log-file");
164  }
165
166  private int extractPort(String portAttr, int index)
167  {
168    int port = -1;
169    String attrWithPoints = portAttr + ":";
170    int index1 = contents.indexOf(attrWithPoints, index);
171    if (index1 != -1) {
172      int index2 =
173        contents.indexOf(Constants.LINE_SEPARATOR, index1);
174      if (index2 != -1) {
175        String sPort =
176          contents.substring(attrWithPoints.length() +
177              index1, index2).trim();
178        try {
179          port = Integer.parseInt(sPort);
180        } catch (NumberFormatException nfe) {
181          // do nothing;
182        }
183      }
184    }
185    return port;
186  }
187
188
189  private int getLDAPPort(String portAttr) throws IOException {
190    String contents = getLowerCaseContents();
191    int index = contents.indexOf("cn=ldap connection handler");
192    if (index != -1) {
193      return extractPort (portAttr, index);
194    }
195    return -1;
196  }
197
198  private int getAdminConnectorPort(String portAttr) throws IOException {
199    String contents = getLowerCaseContents();
200    int index = contents.indexOf("cn=administration connector");
201    if (index != -1) {
202      return extractPort(portAttr, index);
203    }
204    return -1;
205  }
206
207  /**
208   * Indicates whether the config.ldif file has been modified (compared to what
209   * we had in the zip file). This is used to know if we have configured the
210   * current binaries or not.
211   *
212   * @return <CODE>true</CODE> if the config.ldif file has been modified, or
213   *         <CODE>false</CODE> if not.
214   * @throws IOException if there were problems reading the information from
215   * the configuration file.
216   */
217  public boolean hasBeenModified() throws IOException {
218    boolean isConfigFileModified = getPort() != 389;
219
220    if (!isConfigFileModified) {
221      // TODO: this is not really stable
222      // Note: a better way might be to diff this file with
223      // /config/ldif/upgrade/config.ldif.<svn rev>
224      isConfigFileModified =
225          !getLowerCaseContents().contains("# cddl header start");
226    }
227
228    return isConfigFileModified;
229  }
230
231  /**
232   * Returns a Set of relative paths containing the log paths outside the
233   * installation.
234   * @return a Set of relative paths containing the log paths outside the
235   * installation.
236   * @throws IOException if there is trouble reading the config file
237   */
238  public Set<String> getOutsideLogs()
239          throws IOException
240  {
241    return getOutsidePaths(getLogPaths());
242  }
243
244  /**
245   * Returns a Set of relative paths containing the db paths outside the
246   * installation.
247   * @return a Set of relative paths containing the db paths outside the
248   * installation.
249   * @throws IOException if there is trouble reading the config file
250   */
251  public Set<String> getOutsideDbs()
252          throws IOException
253  {
254    return getOutsidePaths(getDatabasePaths());
255  }
256
257  private Set<String> getOutsidePaths(Set<String> paths) {
258    Set<String> outsidePaths = new HashSet<>();
259    for (String path : paths) {
260      File fullDbPath;
261      File pathFile = new File(path);
262      if (pathFile.isAbsolute()) {
263        fullDbPath = pathFile;
264      } else {
265        fullDbPath = new File(install.getInstanceDirectory(), path);
266      }
267
268      if (!Utils.isDescendant(fullDbPath, install.getInstanceDirectory())) {
269        outsidePaths.add(Utils.getPath(fullDbPath));
270      }
271    }
272    return outsidePaths;
273  }
274
275  /**
276   * Provides the contents of the config.ldif file in a String.
277   *
278   * @return a String representing the contents of the config.ldif file.
279   * @throws IOException if there was a problem reading the file
280   */
281  public String getContents() throws IOException {
282    if (contents == null) {
283      load();
284    }
285    return contents;
286  }
287
288  /**
289   * Provides the contents of the config.ldif file in a lower case String.
290   *
291   * @return a lower case String representing the contents of the config.ldif
292   * file.
293   * @throws IOException if there was a problem reading the file
294   */
295  public String getLowerCaseContents() throws IOException {
296    if (lowerCaseContents == null) {
297      load();
298    }
299    return lowerCaseContents;
300  }
301
302  /**
303   * Returns the list of paths where the databases are installed as they appear
304   * in the configuration file.
305   *
306   * @return the list of paths where the databases are installed as they appear
307   * in the configuration file.
308   * @throws IOException if there is a problem reading the config file.
309   */
310  public Set<String> getDatabasePaths() throws IOException {
311    return getConfigurationValues("ds-cfg-db-directory");
312  }
313
314  /**
315   * Returns the list of base dns as they appear in the configuration file.
316   *
317   * @return the list of base dns as they appear in the configuration file.
318   * @throws IOException if there is a problem reading the config file.
319   */
320  public Set<String> getBaseDNs() throws IOException {
321    return getConfigurationValues("ds-cfg-base-dn");
322  }
323
324  /**
325   * Loads the contents of the configuration file into memory.
326   * @throws IOException if there were problems loading the file
327   */
328  public void load() throws IOException {
329    StringBuilder buf = new StringBuilder();
330    FileReader reader = new FileReader(file);
331    BufferedReader in = new BufferedReader(reader);
332    String line;
333    // We do not care about encoding: we are just interested in the ports
334    while ((line = in.readLine()) != null) {
335      buf.append(line).append(Constants.LINE_SEPARATOR);
336    }
337    reader.close();
338    contents = buf.toString();
339    lowerCaseContents = contents.toLowerCase();
340  }
341
342  private Set<String> getConfigurationValues(String attrName)
343          throws IOException
344  {
345    Set<String> set = new HashSet<>();
346    attrName += ":";
347    String lowerCaseContents = getLowerCaseContents();
348    String contents = getContents();
349    int index1 = lowerCaseContents.indexOf(attrName);
350    while (index1 != -1) {
351      int index2 = lowerCaseContents.indexOf(Constants.LINE_SEPARATOR, index1);
352      String value;
353      if (index2 > index1 + attrName.length()) {
354        value = contents.substring(attrName.length() + index1, index2).trim();
355      } else if (lowerCaseContents.length() > index1 + attrName.length()) {
356        // Assume end of file
357        value = contents.substring(attrName.length() + index1).trim();
358      } else {
359        value = null;
360      }
361
362      if (value != null && value.length() > 0) {
363        set.add(value);
364      }
365
366      index1 = lowerCaseContents.indexOf(attrName,
367              index1 + attrName.length());
368    }
369    return set;
370  }
371}