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