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