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-2017 ForgeRock AS.
016 */
017package org.opends.server.config;
018
019import static org.opends.messages.AdminMessages.*;
020
021import java.io.File;
022import java.io.FileWriter;
023import java.io.PrintWriter;
024import java.net.InetAddress;
025import java.util.List;
026import java.util.SortedSet;
027
028import javax.naming.ldap.Rdn;
029
030import org.forgerock.i18n.LocalizableMessage;
031import org.forgerock.i18n.slf4j.LocalizedLogger;
032import org.forgerock.opendj.config.server.ConfigChangeResult;
033import org.forgerock.opendj.config.server.ConfigException;
034import org.forgerock.opendj.config.server.ConfigurationChangeListener;
035import org.forgerock.opendj.ldap.AddressMask;
036import org.forgerock.opendj.ldap.DN;
037import org.forgerock.opendj.server.config.meta.LDAPConnectionHandlerCfgDefn.SSLClientAuthPolicy;
038import org.forgerock.opendj.server.config.server.AdministrationConnectorCfg;
039import org.forgerock.opendj.server.config.server.ConnectionHandlerCfg;
040import org.forgerock.opendj.server.config.server.FileBasedKeyManagerProviderCfg;
041import org.forgerock.opendj.server.config.server.FileBasedTrustManagerProviderCfg;
042import org.forgerock.opendj.server.config.server.KeyManagerProviderCfg;
043import org.forgerock.opendj.server.config.server.LDAPConnectionHandlerCfg;
044import org.forgerock.opendj.server.config.server.RootCfg;
045import org.forgerock.opendj.server.config.server.TrustManagerProviderCfg;
046import org.opends.server.core.DirectoryServer;
047import org.opends.server.core.ServerContext;
048import org.opends.server.core.SynchronousStrategy;
049import org.opends.server.protocols.ldap.LDAPConnectionHandler;
050import org.opends.server.types.DirectoryException;
051import org.opends.server.types.FilePermission;
052import org.opends.server.types.InitializationException;
053import org.opends.server.util.CertificateManager;
054import org.opends.server.util.Platform.KeyType;
055import org.opends.server.util.SetupUtils;
056
057/**
058 * This class is a wrapper on top of LDAPConnectionHandler to manage
059 * the administration connector, which is an LDAPConnectionHandler
060 * with specific (limited) configuration properties.
061 */
062public final class AdministrationConnector implements
063    ConfigurationChangeListener<AdministrationConnectorCfg>
064{
065
066  /** Default Administration Connector port. */
067  public static final int DEFAULT_ADMINISTRATION_CONNECTOR_PORT = 4444;
068  /** Validity (in days) of the generated certificate. */
069  public static final int ADMIN_CERT_VALIDITY = 20 * 365;
070
071  /** Friendly name of the administration connector. */
072  private static final String FRIENDLY_NAME = "Administration Connector";
073  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
074
075  private LDAPConnectionHandler adminConnectionHandler;
076  private AdministrationConnectorCfg config;
077
078  /** Predefined values for Administration Connector configuration. */
079  private static final String ADMIN_CLASS_NAME =
080    "org.opends.server.protocols.ldap.LDAPConnectionHandler";
081
082  private static final boolean ADMIN_ALLOW_LDAP_V2 = false;
083  private static final boolean ADMIN_ALLOW_START_TLS = false;
084
085  private static final boolean ADMIN_ENABLED = true;
086  private static final boolean ADMIN_KEEP_STATS = true;
087  private static final boolean ADMIN_USE_SSL = true;
088
089  private static final int ADMIN_ACCEPT_BACKLOG = 128;
090  private static final boolean ADMIN_ALLOW_TCP_REUSE_ADDRESS = true;
091
092  /** 2mn. */
093  private static final long ADMIN_MAX_BLOCKED_WRITE_TIME_LIMIT = 120000;
094  /** 5 Mb. */
095  private static final int ADMIN_MAX_REQUEST_SIZE = 5000000;
096  private static final int ADMIN_WRITE_BUFFER_SIZE = 4096;
097  private static final int ADMIN_NUM_REQUEST_HANDLERS = 1;
098  private static final boolean ADMIN_SEND_REJECTION_NOTICE = true;
099  private static final boolean ADMIN_USE_TCP_KEEP_ALIVE = true;
100  private static final boolean ADMIN_USE_TCP_NO_DELAY = true;
101  private static final SSLClientAuthPolicy ADMIN_SSL_CLIENT_AUTH_POLICY =
102    SSLClientAuthPolicy.DISABLED;
103
104  private final ServerContext serverContext;
105
106  /**
107   * Initializes this administration connector provider based on the
108   * information in the provided administration connector
109   * configuration.
110   *
111   * @param configuration
112   *          The connection handler configuration that contains the
113   *          information to use to initialize this connection
114   *          handler.
115   * @throws ConfigException
116   *           If an unrecoverable problem arises in the process of
117   *           performing the initialization as a result of the server
118   *           configuration.
119   * @throws InitializationException
120   *           If a problem occurs during initialization that is not
121   *           related to the server configuration.
122   */
123  public void initializeAdministrationConnector(
124      AdministrationConnectorCfg configuration) throws ConfigException,
125      InitializationException
126  {
127    this.config = configuration;
128
129    // Administration Connector uses the LDAP connection handler implementation
130    adminConnectionHandler = new LDAPConnectionHandler(
131        new SynchronousStrategy(), FRIENDLY_NAME);
132    adminConnectionHandler.initializeConnectionHandler(serverContext, new LDAPConnectionCfgAdapter(config));
133    adminConnectionHandler.setAdminConnectionHandler();
134
135    // Register this as a change listener.
136    config.addChangeListener(this);
137  }
138
139
140  /**
141   * Creates an instance of the administration connector.
142   *
143   * @param serverContext
144   *            The server context.
145   **/
146  public AdministrationConnector(ServerContext serverContext)
147  {
148    this.serverContext = serverContext;
149  }
150
151  /**
152   * Retrieves the connection handler linked to this administration connector.
153   *
154   * @return The connection handler linked to this administration connector.
155   */
156  public LDAPConnectionHandler getConnectionHandler()
157  {
158    return adminConnectionHandler;
159  }
160
161  /** {@inheritDoc} */
162  @Override
163  public boolean isConfigurationChangeAcceptable(
164      AdministrationConnectorCfg configuration,
165      List<LocalizableMessage> unacceptableReasons)
166  {
167    return adminConnectionHandler.isConfigurationAcceptable(new LDAPConnectionCfgAdapter(configuration),
168        unacceptableReasons);
169  }
170
171  /** {@inheritDoc} */
172  @Override
173  public ConfigChangeResult applyConfigurationChange(
174      AdministrationConnectorCfg configuration)
175  {
176    return adminConnectionHandler.applyConfigurationChange(new LDAPConnectionCfgAdapter(configuration));
177  }
178
179
180
181  /**
182   * This private class implements a fake LDAP connection Handler configuration.
183   * This allows to re-use the LDAPConnectionHandler as it is.
184   */
185  private static class LDAPConnectionCfgAdapter implements LDAPConnectionHandlerCfg
186  {
187    private final AdministrationConnectorCfg config;
188
189    public LDAPConnectionCfgAdapter(AdministrationConnectorCfg config)
190    {
191      this.config = config;
192    }
193
194    /** {@inheritDoc} */
195    @Override
196    public Class<? extends LDAPConnectionHandlerCfg> configurationClass()
197    {
198      return LDAPConnectionHandlerCfg.class;
199    }
200
201    /** {@inheritDoc} */
202    @Override
203    public void addLDAPChangeListener(
204        ConfigurationChangeListener<LDAPConnectionHandlerCfg> listener)
205    {
206      // do nothing. change listener already added.
207    }
208
209    /** {@inheritDoc} */
210    @Override
211    public void removeLDAPChangeListener(
212        ConfigurationChangeListener<LDAPConnectionHandlerCfg> listener)
213    {
214      // do nothing. change listener already added.
215    }
216
217    /** {@inheritDoc} */
218    @Override
219    public int getAcceptBacklog()
220    {
221      return ADMIN_ACCEPT_BACKLOG;
222    }
223
224    /** {@inheritDoc} */
225    @Override
226    public boolean isAllowLDAPV2()
227    {
228      return ADMIN_ALLOW_LDAP_V2;
229    }
230
231    /** {@inheritDoc} */
232    @Override
233    public boolean isAllowStartTLS()
234    {
235      return ADMIN_ALLOW_START_TLS;
236    }
237
238    /** {@inheritDoc} */
239    @Override
240    public boolean isAllowTCPReuseAddress()
241    {
242      return ADMIN_ALLOW_TCP_REUSE_ADDRESS;
243    }
244
245    /** {@inheritDoc} */
246    @Override
247    public String getJavaClass()
248    {
249      return ADMIN_CLASS_NAME;
250    }
251
252    /** {@inheritDoc} */
253    @Override
254    public boolean isKeepStats()
255    {
256      return ADMIN_KEEP_STATS;
257    }
258
259    /** {@inheritDoc} */
260    @Override
261    public String getKeyManagerProvider()
262    {
263      return config.getKeyManagerProvider();
264    }
265
266    /** {@inheritDoc} */
267    @Override
268    public DN getKeyManagerProviderDN()
269    {
270      return config.getKeyManagerProviderDN();
271    }
272
273    /** {@inheritDoc} */
274    @Override
275    public SortedSet<InetAddress> getListenAddress()
276    {
277      return config.getListenAddress();
278    }
279
280    /** {@inheritDoc} */
281    @Override
282    public int getListenPort()
283    {
284      return config.getListenPort();
285    }
286
287    /** {@inheritDoc} */
288    @Override
289    public long getMaxBlockedWriteTimeLimit()
290    {
291      return ADMIN_MAX_BLOCKED_WRITE_TIME_LIMIT;
292    }
293
294    /** {@inheritDoc} */
295    @Override
296    public long getMaxRequestSize()
297    {
298      return ADMIN_MAX_REQUEST_SIZE;
299    }
300
301    /** {@inheritDoc} */
302    @Override
303    public long getBufferSize()
304    {
305      return ADMIN_WRITE_BUFFER_SIZE;
306    }
307
308    /** {@inheritDoc} */
309    @Override
310    public Integer getNumRequestHandlers()
311    {
312      return ADMIN_NUM_REQUEST_HANDLERS;
313    }
314
315    /** {@inheritDoc} */
316    @Override
317    public boolean isSendRejectionNotice()
318    {
319      return ADMIN_SEND_REJECTION_NOTICE;
320    }
321
322    /** {@inheritDoc} */
323    @Override
324    public SortedSet<String> getSSLCertNickname()
325    {
326      return config.getSSLCertNickname();
327    }
328
329    /** {@inheritDoc} */
330    @Override
331    public SortedSet<String> getSSLCipherSuite()
332    {
333      return config.getSSLCipherSuite();
334    }
335
336    /** {@inheritDoc} */
337    @Override
338    public SSLClientAuthPolicy getSSLClientAuthPolicy()
339    {
340      return ADMIN_SSL_CLIENT_AUTH_POLICY;
341    }
342
343    /** {@inheritDoc} */
344    @Override
345    public SortedSet<String> getSSLProtocol()
346    {
347      return config.getSSLProtocol();
348    }
349
350    /** {@inheritDoc} */
351    @Override
352    public String getTrustManagerProvider()
353    {
354      return config.getTrustManagerProvider();
355    }
356
357    /** {@inheritDoc} */
358    @Override
359    public DN getTrustManagerProviderDN()
360    {
361      return config.getTrustManagerProviderDN();
362    }
363
364    /** {@inheritDoc} */
365    @Override
366    public boolean isUseSSL()
367    {
368      return ADMIN_USE_SSL;
369    }
370
371    /** {@inheritDoc} */
372    @Override
373    public boolean isUseTCPKeepAlive()
374    {
375      return ADMIN_USE_TCP_KEEP_ALIVE;
376    }
377
378    /** {@inheritDoc} */
379    @Override
380    public boolean isUseTCPNoDelay()
381    {
382      return ADMIN_USE_TCP_NO_DELAY;
383    }
384
385    /** {@inheritDoc} */
386    @Override
387    public void addChangeListener(
388        ConfigurationChangeListener<ConnectionHandlerCfg> listener)
389    {
390      // do nothing. change listener already added.
391    }
392
393    /** {@inheritDoc} */
394    @Override
395    public void removeChangeListener(
396        ConfigurationChangeListener<ConnectionHandlerCfg> listener)
397    {
398      // do nothing. change listener already added.
399    }
400
401    /** {@inheritDoc} */
402    @Override
403    public SortedSet<AddressMask> getAllowedClient()
404    {
405      return config.getAllowedClient();
406    }
407
408    /** {@inheritDoc} */
409    @Override
410    public SortedSet<AddressMask> getDeniedClient()
411    {
412      return config.getDeniedClient();
413    }
414
415    /** {@inheritDoc} */
416    @Override
417    public boolean isEnabled()
418    {
419      return ADMIN_ENABLED;
420    }
421
422    /** {@inheritDoc} */
423    @Override
424    public DN dn()
425    {
426      return config.dn();
427    }
428  }
429
430
431
432  /**
433   * Creates a self-signed JKS certificate if needed.
434   *
435   * @param serverContext
436   *          The server context.
437   * @throws InitializationException
438   *           If an unexpected error occurred whilst trying to create the
439   *           certificate.
440   */
441  public static void createSelfSignedCertificateIfNeeded(ServerContext serverContext)
442      throws InitializationException
443  {
444    try
445    {
446      RootCfg root = serverContext.getRootConfig();
447      AdministrationConnectorCfg config = root.getAdministrationConnector();
448
449      // Check if certificate generation is needed
450      final SortedSet<String> certAliases = config.getSSLCertNickname();
451      KeyManagerProviderCfg keyMgrConfig = root.getKeyManagerProvider(config
452          .getKeyManagerProvider());
453      TrustManagerProviderCfg trustMgrConfig = root
454          .getTrustManagerProvider(config.getTrustManagerProvider());
455
456      if (hasDefaultConfigChanged(keyMgrConfig, trustMgrConfig))
457      {
458        // nothing to do
459        return;
460      }
461
462      FileBasedKeyManagerProviderCfg fbKeyManagerConfig =
463        (FileBasedKeyManagerProviderCfg) keyMgrConfig;
464      String keystorePath = getFullPath(fbKeyManagerConfig.getKeyStoreFile());
465      FileBasedTrustManagerProviderCfg fbTrustManagerConfig =
466        (FileBasedTrustManagerProviderCfg) trustMgrConfig;
467      String truststorePath = getFullPath(fbTrustManagerConfig
468          .getTrustStoreFile());
469      String pinFilePath = getFullPath(fbKeyManagerConfig.getKeyStorePinFile());
470
471      // Check that either we do not have any file,
472      // or we have the 3 required files (keystore, truststore, pin
473      // file)
474      boolean keystore = false;
475      boolean truststore = false;
476      boolean pinFile = false;
477      int nbFiles = 0;
478      if (new File(keystorePath).exists())
479      {
480        keystore = true;
481        nbFiles++;
482      }
483      if (new File(truststorePath).exists())
484      {
485        truststore = true;
486        nbFiles++;
487      }
488      if (new File(pinFilePath).exists())
489      {
490        pinFile = true;
491        nbFiles++;
492      }
493      if (nbFiles == 3)
494      {
495        // nothing to do
496        return;
497      }
498      if (nbFiles != 0)
499      {
500        // 1 or 2 files are missing : error
501        String err = "";
502        if (!keystore)
503        {
504          err += keystorePath + " ";
505        }
506        if (!truststore)
507        {
508          err += truststorePath + " ";
509        }
510        if (!pinFile)
511        {
512          err += pinFilePath + " ";
513        }
514        LocalizableMessage message = ERR_ADMIN_CERTIFICATE_GENERATION_MISSING_FILES
515            .get(err);
516        logger.error(message);
517        throw new InitializationException(message);
518      }
519
520      // Generate a password
521      String pwd = new String(SetupUtils.createSelfSignedCertificatePwd());
522
523      // Generate a self-signed certificate
524      CertificateManager certManager = new CertificateManager(
525          getFullPath(fbKeyManagerConfig.getKeyStoreFile()), fbKeyManagerConfig
526              .getKeyStoreType(), pwd);
527      String hostName =
528        SetupUtils.getHostNameForCertificate(DirectoryServer.getServerRoot());
529
530      // Temporary exported certificate's file
531      String tempCertPath = getFullPath("config" + File.separator
532          + "admin-cert.txt");
533
534      // Create a new trust store and import the server certificate
535      // into it
536      CertificateManager trustManager = new CertificateManager(truststorePath,
537          CertificateManager.KEY_STORE_TYPE_JKS, pwd);
538      for (String certAlias : certAliases)
539      {
540        final KeyType keyType = KeyType.getTypeOrDefault(certAlias);
541        final String subjectDN =
542            "cn=" + Rdn.escapeValue(hostName) + ",O=" + FRIENDLY_NAME + " " + keyType + " Self-Signed Certificate";
543        certManager.generateSelfSignedCertificate(keyType, certAlias, subjectDN, ADMIN_CERT_VALIDITY);
544
545        SetupUtils.exportCertificate(certManager, certAlias, tempCertPath);
546
547        // import the server certificate into it
548        final File tempCertFile = new File(tempCertPath);
549        trustManager.addCertificate(certAlias, tempCertFile);
550        tempCertFile.delete();
551      }
552
553      // Generate a password file
554      if (!new File(pinFilePath).exists())
555      {
556        try (final FileWriter file = new FileWriter(pinFilePath);
557             final PrintWriter out = new PrintWriter(file))
558        {
559          out.println(pwd);
560          out.flush();
561        }
562      }
563
564      // Change the password file permission if possible
565      try
566      {
567        if (!FilePermission.setPermissions(new File(pinFilePath),
568            new FilePermission(0600)))
569        {
570          // Log a warning that the permissions were not set.
571          logger.warn(WARN_ADMIN_SET_PERMISSIONS_FAILED, pinFilePath);
572        }
573      }
574      catch (DirectoryException e)
575      {
576        // Log a warning that the permissions were not set.
577        logger.warn(WARN_ADMIN_SET_PERMISSIONS_FAILED, pinFilePath);
578      }
579    }
580    catch (InitializationException e)
581    {
582      throw e;
583    }
584    catch (Exception e)
585    {
586      throw new InitializationException(ERR_ADMIN_CERTIFICATE_GENERATION.get(e.getMessage()), e);
587    }
588  }
589
590  /**
591   * Check if default configuration for administrator's key manager and trust
592   * manager provider has changed.
593   *
594   * @param keyConfig
595   *          key manager provider configuration
596   * @param trustConfig
597   *          trust manager provider configuration
598   * @return true if default configuration has changed, false otherwise
599   */
600  private static boolean hasDefaultConfigChanged(
601      KeyManagerProviderCfg keyConfig, TrustManagerProviderCfg trustConfig)
602  {
603    if (keyConfig.isEnabled()
604        && keyConfig instanceof FileBasedKeyManagerProviderCfg
605        && trustConfig.isEnabled()
606        && trustConfig instanceof FileBasedTrustManagerProviderCfg)
607    {
608      FileBasedKeyManagerProviderCfg fileKeyConfig =
609          (FileBasedKeyManagerProviderCfg) keyConfig;
610      boolean pinIsProvidedByFileOnly =
611          fileKeyConfig.getKeyStorePinFile() != null
612              && fileKeyConfig.getKeyStorePin() == null
613              && fileKeyConfig.getKeyStorePinEnvironmentVariable() == null
614              && fileKeyConfig.getKeyStorePinProperty() == null;
615      return !pinIsProvidedByFileOnly;
616    }
617    return true;
618  }
619
620  private static String getFullPath(String path)
621  {
622    File file = new File(path);
623    if (!file.isAbsolute())
624    {
625      path = DirectoryServer.getInstanceRoot() + File.separator + path;
626    }
627
628    return path;
629  }
630}