001/*
002 * Copyright 2011-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2011-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2011-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.listener;
037
038
039
040import java.io.OutputStream;
041import java.util.List;
042import javax.net.ssl.SSLSocketFactory;
043
044import com.unboundid.asn1.ASN1Buffer;
045import com.unboundid.ldap.protocol.AbandonRequestProtocolOp;
046import com.unboundid.ldap.protocol.AddRequestProtocolOp;
047import com.unboundid.ldap.protocol.BindRequestProtocolOp;
048import com.unboundid.ldap.protocol.CompareRequestProtocolOp;
049import com.unboundid.ldap.protocol.DeleteRequestProtocolOp;
050import com.unboundid.ldap.protocol.ExtendedRequestProtocolOp;
051import com.unboundid.ldap.protocol.ExtendedResponseProtocolOp;
052import com.unboundid.ldap.protocol.ModifyRequestProtocolOp;
053import com.unboundid.ldap.protocol.ModifyDNRequestProtocolOp;
054import com.unboundid.ldap.protocol.SearchRequestProtocolOp;
055import com.unboundid.ldap.protocol.UnbindRequestProtocolOp;
056import com.unboundid.ldap.protocol.LDAPMessage;
057import com.unboundid.ldap.sdk.Control;
058import com.unboundid.ldap.sdk.ExtendedRequest;
059import com.unboundid.ldap.sdk.LDAPException;
060import com.unboundid.ldap.sdk.ResultCode;
061import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
062import com.unboundid.util.Debug;
063import com.unboundid.util.NotNull;
064import com.unboundid.util.Nullable;
065import com.unboundid.util.StaticUtils;
066import com.unboundid.util.ThreadSafety;
067import com.unboundid.util.ThreadSafetyLevel;
068
069import static com.unboundid.ldap.listener.ListenerMessages.*;
070
071
072
073/**
074 * This class provides a request handler implementation that can be used to
075 * convert an existing connection to use TLS encryption.  It will handle
076 * StartTLS extended operations directly, but will pass all other requests and
077 * responses through to another request handler.
078 */
079@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
080public final class StartTLSRequestHandler
081       extends LDAPListenerRequestHandler
082{
083  // Indicates whether the listener should request that the client provide a
084  // certificate.
085  private final boolean requestClientCertificate;
086
087  // Indicates whether the listener should require that the client provide a
088  // certificate.
089  private final boolean requireClientCertificate;
090
091  // The client connection with which this request handler is associated.
092  @Nullable private final LDAPListenerClientConnection connection;
093
094  // The request handler that will be used to process all operations except the
095  // StartTLS extended operation.
096  @NotNull private final LDAPListenerRequestHandler requestHandler;
097
098  // The SSL socket factory that will be used to SSL-enable the existing socket.
099  @NotNull private final SSLSocketFactory sslSocketFactory;
100
101
102
103  /**
104   * Creates a new StartTLS request handler with the provided information.
105   *
106   * @param  sslSocketFactory  The SSL socket factory that will be used to
107   *                           convert the existing socket to use SSL
108   *                           encryption.
109   * @param  requestHandler    The request handler that will be used to process
110   *                           all operations except StartTLS extended
111   *                           operations.
112   */
113  public StartTLSRequestHandler(
114              @NotNull final SSLSocketFactory sslSocketFactory,
115              @NotNull final LDAPListenerRequestHandler requestHandler)
116  {
117    this(sslSocketFactory, requestHandler, false, false);
118  }
119
120
121
122  /**
123   * Creates a new StartTLS request handler with the provided information.
124   *
125   * @param  sslSocketFactory          The SSL socket factory that will be used
126   *                                   to convert the existing socket to use SSL
127   *                                   encryption.
128   * @param  requestHandler            The request handler that will be used to
129   *                                   process all operations except StartTLS
130   *                                   extended operations.
131   * @param  requestClientCertificate  Indicates whether the listener should
132   *                                   request that the client present its own
133   *                                   certificate chain during TLS negotiation.
134   *                                   This will be ignored for non-TLS-based
135   *                                   connections.
136   * @param  requireClientCertificate  Indicates whether the listener should
137   *                                   require that the client present its own
138   *                                   certificate chain during TLS negotiation,
139   *                                   and should fail negotiation if the client
140   *                                   does not present one.  This will be
141   *                                   ignored for non-TLS-based connections or
142   *                                   if {@code requestClientCertificate} is
143   *                                   {@code false}.
144   */
145  public StartTLSRequestHandler(
146              @NotNull final SSLSocketFactory sslSocketFactory,
147              @NotNull final LDAPListenerRequestHandler requestHandler,
148              final boolean requestClientCertificate,
149              final boolean requireClientCertificate)
150  {
151    this.sslSocketFactory         = sslSocketFactory;
152    this.requestHandler           = requestHandler;
153    this.requestClientCertificate = requestClientCertificate;
154    this.requireClientCertificate = requireClientCertificate;
155
156    connection = null;
157  }
158
159
160
161  /**
162   * Creates a new StartTLS request handler with the provided information.
163   *
164   * @param  sslSocketFactory          The SSL socket factory that will be used
165   *                                   to convert the existing socket to use SSL
166   *                                   encryption.
167   * @param  requestHandler            The request handler that will be used to
168   *                                   process all operations except StartTLS
169   *                                   extended operations.
170   * @param  connection        The connection to the associated client.
171   * @param  requestClientCertificate  Indicates whether the listener should
172   *                                   request that the client present its own
173   *                                   certificate chain during TLS negotiation.
174   *                                   This will be ignored for non-TLS-based
175   *                                   connections.
176   * @param  requireClientCertificate  Indicates whether the listener should
177   *                                   require that the client present its own
178   *                                   certificate chain during TLS negotiation,
179   *                                   and should fail negotiation if the client
180   *                                   does not present one.  This will be
181   *                                   ignored for non-TLS-based connections or
182   *                                   if {@code requestClientCertificate} is
183   *                                   {@code false}.
184   */
185  private StartTLSRequestHandler(
186               @NotNull final SSLSocketFactory sslSocketFactory,
187               @NotNull final LDAPListenerRequestHandler requestHandler,
188               @NotNull final LDAPListenerClientConnection connection,
189               final boolean requestClientCertificate,
190               final boolean requireClientCertificate)
191  {
192    this.sslSocketFactory         = sslSocketFactory;
193    this.requestHandler           = requestHandler;
194    this.connection               = connection;
195    this.requestClientCertificate = requestClientCertificate;
196    this.requireClientCertificate = requireClientCertificate;
197  }
198
199
200
201  /**
202   * {@inheritDoc}
203   */
204  @Override()
205  @NotNull()
206  public StartTLSRequestHandler newInstance(
207              @NotNull final LDAPListenerClientConnection connection)
208         throws LDAPException
209  {
210    return new StartTLSRequestHandler(sslSocketFactory,
211         requestHandler.newInstance(connection), connection,
212         requestClientCertificate, requireClientCertificate);
213  }
214
215
216
217  /**
218   * {@inheritDoc}
219   */
220  @Override()
221  public void closeInstance()
222  {
223    requestHandler.closeInstance();
224  }
225
226
227
228  /**
229   * {@inheritDoc}
230   */
231  @Override()
232  public void processAbandonRequest(final int messageID,
233                   @NotNull final AbandonRequestProtocolOp request,
234                   @NotNull final List<Control> controls)
235  {
236    requestHandler.processAbandonRequest(messageID, request, controls);
237  }
238
239
240
241  /**
242   * {@inheritDoc}
243   */
244  @Override()
245  @NotNull()
246  public LDAPMessage processAddRequest(final int messageID,
247                          @NotNull final AddRequestProtocolOp request,
248                          @NotNull final List<Control> controls)
249  {
250    return requestHandler.processAddRequest(messageID, request, controls);
251  }
252
253
254
255  /**
256   * {@inheritDoc}
257   */
258  @Override()
259  @NotNull()
260  public LDAPMessage processBindRequest(final int messageID,
261                          @NotNull final BindRequestProtocolOp request,
262                          @NotNull final List<Control> controls)
263  {
264    return requestHandler.processBindRequest(messageID, request, controls);
265  }
266
267
268
269  /**
270   * {@inheritDoc}
271   */
272  @Override()
273  @NotNull()
274  public LDAPMessage processCompareRequest(final int messageID,
275                          @NotNull final CompareRequestProtocolOp request,
276                          @NotNull final List<Control> controls)
277  {
278    return requestHandler.processCompareRequest(messageID, request, controls);
279  }
280
281
282
283  /**
284   * {@inheritDoc}
285   */
286  @Override()
287  @NotNull()
288  public LDAPMessage processDeleteRequest(final int messageID,
289                          @NotNull final DeleteRequestProtocolOp request,
290                          @NotNull final List<Control> controls)
291  {
292    return requestHandler.processDeleteRequest(messageID, request, controls);
293  }
294
295
296
297  /**
298   * {@inheritDoc}
299   */
300  @Override()
301  @NotNull()
302  public LDAPMessage processExtendedRequest(final int messageID,
303                          @NotNull final ExtendedRequestProtocolOp request,
304                          @NotNull final List<Control> controls)
305  {
306    if (request.getOID().equals(StartTLSExtendedRequest.STARTTLS_REQUEST_OID))
307    {
308      try
309      {
310        // Make sure we can decode the request as a valid StartTLS request.
311        final StartTLSExtendedRequest startTLSRequest =
312             new StartTLSExtendedRequest(new ExtendedRequest(request.getOID(),
313                  request.getValue()));
314
315        final OutputStream clearOutputStream = connection.convertToTLS(
316             sslSocketFactory, requestClientCertificate,
317             requireClientCertificate);
318
319        final LDAPMessage responseMessage = new LDAPMessage(messageID,
320             new ExtendedResponseProtocolOp(ResultCode.SUCCESS_INT_VALUE, null,
321                  null, null, null, null));
322        final ASN1Buffer buffer = new ASN1Buffer();
323        responseMessage.writeTo(buffer);
324
325        try
326        {
327          buffer.writeTo(clearOutputStream);
328          clearOutputStream.flush();
329        }
330        catch (final Exception e)
331        {
332          Debug.debugException(e);
333          final LDAPException le = new LDAPException(ResultCode.LOCAL_ERROR,
334               ERR_START_TLS_REQUEST_HANDLER_WRITE_RESPONSE_FAILURE.get(
335                    StaticUtils.getExceptionMessage(e)),
336               e);
337          connection.close(le);
338          throw le;
339        }
340
341        return responseMessage;
342      }
343      catch (final LDAPException le)
344      {
345        Debug.debugException(le);
346
347        return new LDAPMessage(messageID,
348             new ExtendedResponseProtocolOp(le.getResultCode().intValue(),
349                  le.getMatchedDN(), le.getDiagnosticMessage(),
350                  StaticUtils.toList(le.getReferralURLs()), null, null),
351             le.getResponseControls());
352      }
353    }
354    else
355    {
356      return requestHandler.processExtendedRequest(messageID, request,
357           controls);
358    }
359  }
360
361
362
363  /**
364   * {@inheritDoc}
365   */
366  @Override()
367  @NotNull()
368  public LDAPMessage processModifyRequest(final int messageID,
369                          @NotNull final ModifyRequestProtocolOp request,
370                          @NotNull final List<Control> controls)
371  {
372    return requestHandler.processModifyRequest(messageID, request, controls);
373  }
374
375
376
377  /**
378   * {@inheritDoc}
379   */
380  @Override()
381  @NotNull()
382  public LDAPMessage processModifyDNRequest(final int messageID,
383                          @NotNull final ModifyDNRequestProtocolOp request,
384                          @NotNull final List<Control> controls)
385  {
386    return requestHandler.processModifyDNRequest(messageID, request, controls);
387  }
388
389
390
391  /**
392   * {@inheritDoc}
393   */
394  @Override()
395  @NotNull()
396  public LDAPMessage processSearchRequest(final int messageID,
397                          @NotNull final SearchRequestProtocolOp request,
398                          @NotNull final List<Control> controls)
399  {
400    return requestHandler.processSearchRequest(messageID, request, controls);
401  }
402
403
404
405  /**
406   * {@inheritDoc}
407   */
408  @Override()
409  public void processUnbindRequest(final int messageID,
410                   @NotNull final UnbindRequestProtocolOp request,
411                   @NotNull final List<Control> controls)
412  {
413    requestHandler.processUnbindRequest(messageID, request, controls);
414  }
415}