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 2006-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017package org.opends.server.util;
018
019import java.io.BufferedReader;
020import java.io.BufferedWriter;
021import java.io.File;
022import java.io.FileOutputStream;
023import java.io.FileReader;
024import java.io.FileWriter;
025import java.io.IOException;
026import java.net.InetSocketAddress;
027import java.net.ServerSocket;
028import java.net.Socket;
029import java.net.UnknownHostException;
030import java.security.KeyStoreException;
031import java.security.cert.Certificate;
032import java.security.cert.CertificateEncodingException;
033import java.util.HashSet;
034import java.util.LinkedList;
035import java.util.Random;
036import java.util.Set;
037
038import com.forgerock.opendj.util.OperatingSystem;
039
040/**
041 * This class provides a number of utility methods that may be used during the
042 * graphical or command-line setup process.
043 */
044@org.opends.server.types.PublicAPI(
045     stability=org.opends.server.types.StabilityLevel.VOLATILE,
046     mayInstantiate=false,
047     mayExtend=false,
048     mayInvoke=true)
049public class SetupUtils
050{
051  /**
052   * Specific environment variable used by the scripts to find java.
053   */
054  public static final String OPENDJ_JAVA_HOME = "OPENDJ_JAVA_HOME";
055
056  /**
057   * Specific environment variable used by the scripts to set java arguments.
058   */
059  public static final String OPENDJ_JAVA_ARGS = "OPENDJ_JAVA_ARGS";
060
061  /**
062   * The relative path where all the libraries (jar files) are.
063   */
064  public static final String LIBRARIES_PATH_RELATIVE = "lib";
065
066  /**
067   * The relative path where the setup stores the name of the host the user
068   * provides. This is used for instance to generate the self-signed admin
069   * certificate the first time the server starts.
070   */
071  public static final String HOST_NAME_FILE = "config" + File.separatorChar
072      + "hostname";
073
074  /* These string values must be synchronized with Directory Server's main
075   * method.  These string values are considered stable by the server team and
076   * not candidates for internationalization. */
077  /** Product name. */
078  public static final String NAME = "Name";
079  /** Build ID. */
080  public static final String BUILD_ID = "Build ID";
081  /** Major version. */
082  public static final String MAJOR_VERSION = "Major Version";
083  /** Minor version. */
084  public static final String MINOR_VERSION = "Minor Version";
085  /** Point version of the product. */
086  public static final String POINT_VERSION = "Point Version";
087  /** Revision in VCS. */
088  public static final String REVISION = "Revision Number";
089  /** The VCS url repository. */
090  public static final String URL_REPOSITORY = "URL Repository";
091  /** The version qualifier. */
092  public static final String VERSION_QUALIFIER = "Version Qualifier";
093  /** Fix IDs associated with the build. */
094  public static final String FIX_IDS = "Fix IDs";
095  /** Debug build identifier. */
096  public static final String DEBUG_BUILD = "Debug Build";
097  /** The OS used during the build. */
098  public static final String BUILD_OS = "Build OS";
099  /** The user that generated the build. */
100  public static final String BUILD_USER = "Build User";
101  /** The java version used to generate the build. */
102  public static final String BUILD_JAVA_VERSION = "Build Java Version";
103  /** The java vendor of the JVM used to build. */
104  public static final String BUILD_JAVA_VENDOR = "Build Java Vendor";
105  /** The version of the JVM used to create the build. */
106  public static final String BUILD_JVM_VERSION = "Build JVM Version";
107  /** The vendor of the JVM used to create the build. */
108  public static final String BUILD_JVM_VENDOR = "Build JVM Vendor";
109  /** The build number. */
110  public static final String BUILD_NUMBER = "Build Number";
111
112  /**
113   * A variable used to keep the latest read host name from the file written
114   * by the setup.
115   */
116  private static String lastReadHostName;
117
118  /**
119   * Creates a MakeLDIF template file using the provided information.
120   *
121   * @param  baseDN      The base DN for the data in the template file.
122   * @param  numEntries  The number of user entries the template file should
123   *                     create.
124   *
125   * @return  The {@code File} object that references the created template file.
126   *
127   * @throws  IOException  If a problem occurs while writing the template file.
128   */
129  public static File createTemplateFile(String baseDN, int numEntries)
130         throws IOException
131  {
132    Set<String> baseDNs = new HashSet<>(1);
133    baseDNs.add(baseDN);
134    return createTemplateFile(baseDNs, numEntries);
135  }
136
137  /**
138   * Creates a MakeLDIF template file using the provided information.
139   *
140   * @param  baseDNs     The base DNs for the data in the template file.
141   * @param  numEntries  The number of user entries the template file should
142   *                     create.
143   *
144   * @return  The {@code File} object that references the created template file.
145   *
146   * @throws  IOException  If a problem occurs while writing the template file.
147   */
148  public static File createTemplateFile(Set<String> baseDNs,
149      int numEntries)
150         throws IOException
151  {
152    File templateFile = File.createTempFile("opendj-install", ".template");
153    templateFile.deleteOnExit();
154
155    LinkedList<String> lines = new LinkedList<>();
156    int i = 0;
157    for (String baseDN : baseDNs)
158    {
159      i++;
160      lines.add("define suffix"+i+"=" + baseDN);
161    }
162    if (numEntries > 0)
163    {
164      lines.add("define numusers=" + numEntries);
165    }
166
167    for (i=1; i<=baseDNs.size(); i++)
168    {
169      lines.add("");
170      lines.add("branch: [suffix"+i+"]");
171      lines.add("");
172      lines.add("branch: ou=People,[suffix"+i+"]");
173
174      if (numEntries > 0)
175      {
176        lines.add("subordinateTemplate: person:[numusers]");
177        lines.add("");
178      }
179    }
180
181    if (!baseDNs.isEmpty() && numEntries > 0)
182    {
183      lines.add("template: person");
184      lines.add("rdnAttr: uid");
185      lines.add("objectClass: top");
186      lines.add("objectClass: person");
187      lines.add("objectClass: organizationalPerson");
188      lines.add("objectClass: inetOrgPerson");
189      lines.add("givenName: <first>");
190      lines.add("sn: <last>");
191      lines.add("cn: {givenName} {sn}");
192      lines.add("initials: {givenName:1}" +
193      "<random:chars:ABCDEFGHIJKLMNOPQRSTUVWXYZ:1>{sn:1}");
194      lines.add("employeeNumber: <sequential:0>");
195      lines.add("uid: user.{employeeNumber}");
196      lines.add("mail: {uid}@maildomain.net");
197      lines.add("userPassword: password");
198      lines.add("telephoneNumber: <random:telephone>");
199      lines.add("homePhone: <random:telephone>");
200      lines.add("pager: <random:telephone>");
201      lines.add("mobile: <random:telephone>");
202      lines.add("street: <random:numeric:5> <file:streets> Street");
203      lines.add("l: <file:cities>");
204      lines.add("st: <file:states>");
205      lines.add("postalCode: <random:numeric:5>");
206      lines.add("postalAddress: {cn}${street}${l}, {st}  {postalCode}");
207      lines.add("description: This is the description for {cn}.");
208    }
209
210    try (BufferedWriter writer = new BufferedWriter(new FileWriter(templateFile)))
211    {
212      for (String line : lines)
213      {
214        writer.write(line);
215        writer.newLine();
216      }
217    }
218    return templateFile;
219  }
220
221  /**
222   * Returns {@code true} if the provided port is free and we can use it,
223   * {@code false} otherwise.
224   * @param hostname the host name we are analyzing.  Use <CODE>null</CODE>
225   * to connect to any address.
226   * @param port the port we are analyzing.
227   * @return {@code true} if the provided port is free and we can use it,
228   * {@code false} otherwise.
229   */
230  private static boolean canUseAsPort(String hostname, int port)
231  {
232    try (ServerSocket serverSocket = new ServerSocket())
233    {
234      InetSocketAddress socketAddress;
235      if (hostname != null)
236      {
237        socketAddress = new InetSocketAddress(hostname, port);
238      }
239      else
240      {
241        socketAddress = new InetSocketAddress(port);
242      }
243      if (!OperatingSystem.isWindows())
244      {
245        serverSocket.setReuseAddress(true);
246      }
247      serverSocket.bind(socketAddress);
248      serverSocket.close();
249
250      /* Try to create a socket because sometimes even if we can create a server
251       * socket there is already someone listening to the port (is the case
252       * of products as Sun DS 6.0).
253       */
254      try (Socket s = new Socket())
255      {
256        s.connect(socketAddress, 1000);
257        return false;
258      } catch (Throwable t)
259      {
260      }
261      return true;
262    } catch (IOException ex)
263    {
264      return false;
265    }
266  }
267
268  /**
269   * Returns {@code true} if the provided port is free and we can use it,
270   * {@code false} otherwise.
271   * @param port the port we are analyzing.
272   * @return {@code true} if the provided port is free and we can use it,
273   * {@code false} otherwise.
274   */
275  public static boolean canUseAsPort(int port)
276  {
277    return canUseAsPort(null, port);
278  }
279
280  /**
281   * Returns {@code true} if the provided port is a privileged port,
282   * {@code false} otherwise.
283   * @param port the port we are analyzing.
284   * @return {@code true} if the provided port is a privileged port,
285   * {@code false} otherwise.
286   */
287  public static boolean isPrivilegedPort(int port)
288  {
289    return port <= 1024 && !OperatingSystem.isWindows();
290  }
291
292  /**
293   * Returns the String that can be used to launch an script using Runtime.exec.
294   * This method is required because in Windows the script that contain a "="
295   * in their path must be quoted.
296   * @param script the script name
297   * @return the absolute path for the given parentPath and relativePath.
298   */
299  public static String getScriptPath(String script)
300  {
301    String s = script;
302    if (OperatingSystem.isWindows()
303        && s != null && (!s.startsWith("\"") || !s.endsWith("\"")))
304    {
305      return "\"" + script + "\"";
306    }
307    return s;
308  }
309
310  /**
311   * Returns a randomly generated password for a self-signed certificate
312   * keystore.
313   * @return a randomly generated password for a self-signed certificate
314   * keystore.
315   */
316  public static char[] createSelfSignedCertificatePwd() {
317    int pwdLength = 50;
318    char[] pwd = new char[pwdLength];
319    Random random = new Random();
320    for (int pos=0; pos < pwdLength; pos++) {
321        int type = getRandomInt(random,3);
322        char nextChar = getRandomChar(random,type);
323        pwd[pos] = nextChar;
324    }
325    return pwd;
326  }
327
328  /**
329   * Export a certificate in a file. If the certificate alias to export is null,
330   * It will export the first certificate defined.
331   *
332   * @param certManager
333   *          Certificate manager to use.
334   * @param alias
335   *          Certificate alias to export. If {@code null} the first certificate
336   *          defined will be exported.
337   * @param path
338   *          Path of the output file.
339   * @throws CertificateEncodingException
340   *           If the certificate manager cannot encode the certificate.
341   * @throws IOException
342   *           If a problem occurs while creating or writing in the output file.
343   * @throws KeyStoreException
344   *           If the certificate manager cannot retrieve the certificate to be
345   *           exported.
346   */
347  public static void exportCertificate(CertificateManager certManager, String alias, String path)
348      throws CertificateEncodingException, IOException, KeyStoreException
349  {
350    final Certificate certificate =
351        certManager.getCertificate(alias != null ? alias : certManager.getCertificateAliases()[0]);
352    byte[] certificateBytes = certificate.getEncoded();
353
354    try (FileOutputStream outputStream = new FileOutputStream(path, false))
355    {
356      outputStream.write(certificateBytes);
357    }
358  }
359
360
361  /**
362   * The next two methods are used to generate the random password for the
363   * self-signed certificate.
364   */
365  private static char getRandomChar(Random random, int type)
366  {
367    char generatedChar;
368    int next = random.nextInt();
369    int d;
370
371    switch (type)
372    {
373    case 0:
374      // Will return a digit
375      d = next % 10;
376      if (d < 0)
377      {
378        d = d * -1;
379      }
380      generatedChar = (char) (d+48);
381      break;
382    case 1:
383      // Will return a lower case letter
384      d = next % 26;
385      if (d < 0)
386      {
387        d = d * -1;
388      }
389      generatedChar =  (char) (d + 97);
390      break;
391    default:
392      // Will return a capital letter
393      d = next % 26;
394      if (d < 0)
395      {
396        d = d * -1;
397      }
398      generatedChar = (char) (d + 65) ;
399    }
400
401    return generatedChar;
402  }
403
404  private static int getRandomInt(Random random,int modulo)
405  {
406    return random.nextInt() & modulo;
407  }
408
409  /**
410   * Returns the host name to be used to create self-signed certificates. <br>
411   * The method will first try to read the host name file written by the setup
412   * where the user provided the host name where OpenDJ has been installed. If
413   * the file cannot be read, the class {@link java.net.InetAddress} is used.
414   *
415   * @param installationRoot the path where the server is installed.
416   * @return the host name to be used to create self-signed certificates.
417   * @throws UnknownHostException
418   *           if a host name could not be used.
419   */
420  public static String getHostNameForCertificate(
421      String installationRoot) throws UnknownHostException
422  {
423    String hostName = null;
424    File f = new File(installationRoot + File.separator + HOST_NAME_FILE);
425    try (BufferedReader br = new BufferedReader(new FileReader(f)))
426    {
427      String s = br.readLine();
428      s = s.trim();
429
430      if (s.length() > 0)
431      {
432        hostName = s;
433        lastReadHostName = hostName;
434      }
435    }
436    catch (IOException ioe)
437    {
438    }
439    if (hostName == null)
440    {
441      hostName = lastReadHostName;
442    }
443    if (hostName == null)
444    {
445      hostName = java.net.InetAddress.getLocalHost().getHostName();
446    }
447    return hostName;
448  }
449}