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