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