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