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