View Javadoc

1   /*
2    * CDDL HEADER START
3    *
4    * The contents of this file are subject to the terms of the
5    * Common Development and Distribution License, Version 1.0 only
6    * (the "License").  You may not use this file except in compliance
7    * with the License.
8    *
9    * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
10   * or http://forgerock.org/license/CDDLv1.0.html.
11   * See the License for the specific language governing permissions
12   * and limitations under the License.
13   *
14   * When distributing Covered Code, include this CDDL HEADER in each
15   * file and include the License file at legal-notices/CDDLv1_0.txt.
16   * If applicable, add the following below this CDDL HEADER, with the
17   * fields enclosed by brackets "[]" replaced with your own identifying
18   * information:
19   *      Portions Copyright [yyyy] [name of copyright owner]
20   *
21   * CDDL HEADER END
22   *
23   *
24   *      Copyright 2009-2010 Sun Microsystems, Inc.
25   *      Portions copyright 2011-2012 ForgeRock AS
26   */
27  
28  package org.forgerock.opendj.examples;
29  
30  import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
31  
32  import java.io.IOException;
33  import java.util.LinkedList;
34  import java.util.List;
35  
36  import org.forgerock.opendj.ldap.Connection;
37  import org.forgerock.opendj.ldap.ConnectionFactory;
38  import org.forgerock.opendj.ldap.Connections;
39  import org.forgerock.opendj.ldap.ErrorResultException;
40  import org.forgerock.opendj.ldap.IntermediateResponseHandler;
41  import org.forgerock.opendj.ldap.LDAPClientContext;
42  import org.forgerock.opendj.ldap.LDAPConnectionFactory;
43  import org.forgerock.opendj.ldap.LDAPListener;
44  import org.forgerock.opendj.ldap.LDAPListenerOptions;
45  import org.forgerock.opendj.ldap.RequestContext;
46  import org.forgerock.opendj.ldap.RequestHandler;
47  import org.forgerock.opendj.ldap.ResultCode;
48  import org.forgerock.opendj.ldap.ResultHandler;
49  import org.forgerock.opendj.ldap.RoundRobinLoadBalancingAlgorithm;
50  import org.forgerock.opendj.ldap.SearchResultHandler;
51  import org.forgerock.opendj.ldap.ServerConnectionFactory;
52  import org.forgerock.opendj.ldap.controls.ProxiedAuthV2RequestControl;
53  import org.forgerock.opendj.ldap.requests.AddRequest;
54  import org.forgerock.opendj.ldap.requests.BindRequest;
55  import org.forgerock.opendj.ldap.requests.CancelExtendedRequest;
56  import org.forgerock.opendj.ldap.requests.CompareRequest;
57  import org.forgerock.opendj.ldap.requests.DeleteRequest;
58  import org.forgerock.opendj.ldap.requests.ExtendedRequest;
59  import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
60  import org.forgerock.opendj.ldap.requests.ModifyRequest;
61  import org.forgerock.opendj.ldap.requests.Request;
62  import org.forgerock.opendj.ldap.requests.Requests;
63  import org.forgerock.opendj.ldap.requests.SearchRequest;
64  import org.forgerock.opendj.ldap.requests.StartTLSExtendedRequest;
65  import org.forgerock.opendj.ldap.responses.BindResult;
66  import org.forgerock.opendj.ldap.responses.CompareResult;
67  import org.forgerock.opendj.ldap.responses.ExtendedResult;
68  import org.forgerock.opendj.ldap.responses.Result;
69  import org.forgerock.opendj.ldap.responses.SearchResultEntry;
70  import org.forgerock.opendj.ldap.responses.SearchResultReference;
71  
72  /**
73   * An LDAP load balancing proxy which forwards requests to one or more remote
74   * Directory Servers. This is implementation is very simple and is only intended
75   * as an example:
76   * <ul>
77   * <li>It does not support SSL connections
78   * <li>It does not support StartTLS
79   * <li>It does not support Abandon or Cancel requests
80   * <li>Very basic authentication and authorization support.
81   * </ul>
82   * This example takes the following command line parameters:
83   *
84   * <pre>
85   *  &lt;listenAddress> &lt;listenPort> &lt;proxyDN> &ltproxyPassword> &lt;remoteAddress1> &lt;remotePort1>
86   *      [&lt;remoteAddress2> &lt;remotePort2> ...]
87   * </pre>
88   */
89  public final class Proxy {
90      private static final class ProxyBackend implements RequestHandler<RequestContext> {
91          private final ConnectionFactory factory;
92          private final ConnectionFactory bindFactory;
93  
94          private ProxyBackend(final ConnectionFactory factory, final ConnectionFactory bindFactory) {
95              this.factory = factory;
96              this.bindFactory = bindFactory;
97          }
98  
99          private abstract class AbstractRequestCompletionHandler
100                 <R extends Result, H extends ResultHandler<? super R>>
101                 implements ResultHandler<R> {
102             final H resultHandler;
103             final Connection connection;
104 
105             AbstractRequestCompletionHandler(final Connection connection, final H resultHandler) {
106                 this.connection = connection;
107                 this.resultHandler = resultHandler;
108             }
109 
110             @Override
111             public final void handleErrorResult(final ErrorResultException error) {
112                 connection.close();
113                 resultHandler.handleErrorResult(error);
114             }
115 
116             @Override
117             public final void handleResult(final R result) {
118                 connection.close();
119                 resultHandler.handleResult(result);
120             }
121 
122         }
123 
124         private abstract class ConnectionCompletionHandler<R extends Result> implements
125                 ResultHandler<Connection> {
126             private final ResultHandler<? super R> resultHandler;
127 
128             ConnectionCompletionHandler(final ResultHandler<? super R> resultHandler) {
129                 this.resultHandler = resultHandler;
130             }
131 
132             @Override
133             public final void handleErrorResult(final ErrorResultException error) {
134                 resultHandler.handleErrorResult(error);
135             }
136 
137             @Override
138             public abstract void handleResult(Connection connection);
139 
140         }
141 
142         private final class RequestCompletionHandler<R extends Result> extends
143                 AbstractRequestCompletionHandler<R, ResultHandler<? super R>> {
144             RequestCompletionHandler(final Connection connection,
145                     final ResultHandler<? super R> resultHandler) {
146                 super(connection, resultHandler);
147             }
148         }
149 
150         private final class SearchRequestCompletionHandler extends
151                 AbstractRequestCompletionHandler<Result, SearchResultHandler> implements
152                 SearchResultHandler {
153 
154             SearchRequestCompletionHandler(final Connection connection,
155                     final SearchResultHandler resultHandler) {
156                 super(connection, resultHandler);
157             }
158 
159             /**
160              * {@inheritDoc}
161              */
162             @Override
163             public final boolean handleEntry(final SearchResultEntry entry) {
164                 return resultHandler.handleEntry(entry);
165             }
166 
167             /**
168              * {@inheritDoc}
169              */
170             @Override
171             public final boolean handleReference(final SearchResultReference reference) {
172                 return resultHandler.handleReference(reference);
173             }
174 
175         }
176 
177         private volatile ProxiedAuthV2RequestControl proxiedAuthControl = null;
178 
179         /**
180          * {@inheritDoc}
181          */
182         @Override
183         public void handleAdd(final RequestContext requestContext, final AddRequest request,
184                 final IntermediateResponseHandler intermediateResponseHandler,
185                 final ResultHandler<? super Result> resultHandler) {
186             addProxiedAuthControl(request);
187             final ConnectionCompletionHandler<Result> outerHandler =
188                     new ConnectionCompletionHandler<Result>(resultHandler) {
189 
190                         @Override
191                         public void handleResult(final Connection connection) {
192                             final RequestCompletionHandler<Result> innerHandler =
193                                     new RequestCompletionHandler<Result>(connection, resultHandler);
194                             connection.addAsync(request, intermediateResponseHandler, innerHandler);
195                         }
196 
197                     };
198 
199             factory.getConnectionAsync(outerHandler);
200         }
201 
202         /**
203          * {@inheritDoc}
204          */
205         @Override
206         public void handleBind(final RequestContext requestContext, final int version,
207                 final BindRequest request,
208                 final IntermediateResponseHandler intermediateResponseHandler,
209                 final ResultHandler<? super BindResult> resultHandler) {
210 
211             if (request.getAuthenticationType() != ((byte) 0x80)) {
212                 // TODO: SASL authentication not implemented.
213                 resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR,
214                         "non-SIMPLE authentication not supported: "
215                                 + request.getAuthenticationType()));
216             } else {
217                 // Authenticate using a separate bind connection pool, because
218                 // we don't want to change the state of the pooled connection.
219                 final ConnectionCompletionHandler<BindResult> outerHandler =
220                         new ConnectionCompletionHandler<BindResult>(resultHandler) {
221 
222                             @Override
223                             public void handleResult(final Connection connection) {
224                                 final ResultHandler<BindResult> innerHandler =
225                                         new ResultHandler<BindResult>() {
226 
227                                             @Override
228                                             public final void handleErrorResult(
229                                                     final ErrorResultException error) {
230                                                 connection.close();
231                                                 resultHandler.handleErrorResult(error);
232                                             }
233 
234                                             @Override
235                                             public final void handleResult(final BindResult result) {
236                                                 connection.close();
237                                                 proxiedAuthControl =
238                                                         ProxiedAuthV2RequestControl
239                                                                 .newControl("dn:"
240                                                                         + request.getName());
241                                                 resultHandler.handleResult(result);
242                                             }
243                                         };
244                                 connection.bindAsync(request, intermediateResponseHandler,
245                                         innerHandler);
246                             }
247 
248                         };
249 
250                 proxiedAuthControl = null;
251                 bindFactory.getConnectionAsync(outerHandler);
252             }
253         }
254 
255         /**
256          * {@inheritDoc}
257          */
258         @Override
259         public void handleCompare(final RequestContext requestContext,
260                 final CompareRequest request,
261                 final IntermediateResponseHandler intermediateResponseHandler,
262                 final ResultHandler<? super CompareResult> resultHandler) {
263             addProxiedAuthControl(request);
264             final ConnectionCompletionHandler<CompareResult> outerHandler =
265                     new ConnectionCompletionHandler<CompareResult>(resultHandler) {
266 
267                         @Override
268                         public void handleResult(final Connection connection) {
269                             final RequestCompletionHandler<CompareResult> innerHandler =
270                                     new RequestCompletionHandler<CompareResult>(connection,
271                                             resultHandler);
272                             connection.compareAsync(request, intermediateResponseHandler,
273                                     innerHandler);
274                         }
275 
276                     };
277 
278             factory.getConnectionAsync(outerHandler);
279         }
280 
281         /**
282          * {@inheritDoc}
283          */
284         @Override
285         public void handleDelete(final RequestContext requestContext, final DeleteRequest request,
286                 final IntermediateResponseHandler intermediateResponseHandler,
287                 final ResultHandler<? super Result> resultHandler) {
288             addProxiedAuthControl(request);
289             final ConnectionCompletionHandler<Result> outerHandler =
290                     new ConnectionCompletionHandler<Result>(resultHandler) {
291 
292                         @Override
293                         public void handleResult(final Connection connection) {
294                             final RequestCompletionHandler<Result> innerHandler =
295                                     new RequestCompletionHandler<Result>(connection, resultHandler);
296                             connection.deleteAsync(request, intermediateResponseHandler,
297                                     innerHandler);
298                         }
299 
300                     };
301 
302             factory.getConnectionAsync(outerHandler);
303         }
304 
305         /**
306          * {@inheritDoc}
307          */
308         @Override
309         public <R extends ExtendedResult> void handleExtendedRequest(
310                 final RequestContext requestContext, final ExtendedRequest<R> request,
311                 final IntermediateResponseHandler intermediateResponseHandler,
312                 final ResultHandler<? super R> resultHandler) {
313             if (request.getOID().equals(CancelExtendedRequest.OID)) {
314                 // TODO: not implemented.
315                 resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR,
316                         "Cancel extended request operation not supported"));
317             } else if (request.getOID().equals(StartTLSExtendedRequest.OID)) {
318                 // TODO: not implemented.
319                 resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR,
320                         "StartTLS extended request operation not supported"));
321             } else {
322                 // Forward all other extended operations.
323                 addProxiedAuthControl(request);
324 
325                 final ConnectionCompletionHandler<R> outerHandler =
326                         new ConnectionCompletionHandler<R>(resultHandler) {
327 
328                             @Override
329                             public void handleResult(final Connection connection) {
330                                 final RequestCompletionHandler<R> innerHandler =
331                                         new RequestCompletionHandler<R>(connection, resultHandler);
332                                 connection.extendedRequestAsync(request,
333                                         intermediateResponseHandler, innerHandler);
334                             }
335 
336                         };
337 
338                 factory.getConnectionAsync(outerHandler);
339             }
340         }
341 
342         /**
343          * {@inheritDoc}
344          */
345         @Override
346         public void handleModify(final RequestContext requestContext, final ModifyRequest request,
347                 final IntermediateResponseHandler intermediateResponseHandler,
348                 final ResultHandler<? super Result> resultHandler) {
349             addProxiedAuthControl(request);
350             final ConnectionCompletionHandler<Result> outerHandler =
351                     new ConnectionCompletionHandler<Result>(resultHandler) {
352 
353                         @Override
354                         public void handleResult(final Connection connection) {
355                             final RequestCompletionHandler<Result> innerHandler =
356                                     new RequestCompletionHandler<Result>(connection, resultHandler);
357                             connection.modifyAsync(request, intermediateResponseHandler,
358                                     innerHandler);
359                         }
360 
361                     };
362 
363             factory.getConnectionAsync(outerHandler);
364         }
365 
366         /**
367          * {@inheritDoc}
368          */
369         @Override
370         public void handleModifyDN(final RequestContext requestContext,
371                 final ModifyDNRequest request,
372                 final IntermediateResponseHandler intermediateResponseHandler,
373                 final ResultHandler<? super Result> resultHandler) {
374             addProxiedAuthControl(request);
375             final ConnectionCompletionHandler<Result> outerHandler =
376                     new ConnectionCompletionHandler<Result>(resultHandler) {
377 
378                         @Override
379                         public void handleResult(final Connection connection) {
380                             final RequestCompletionHandler<Result> innerHandler =
381                                     new RequestCompletionHandler<Result>(connection, resultHandler);
382                             connection.modifyDNAsync(request, intermediateResponseHandler,
383                                     innerHandler);
384                         }
385 
386                     };
387 
388             factory.getConnectionAsync(outerHandler);
389         }
390 
391         /**
392          * {@inheritDoc}
393          */
394         @Override
395         public void handleSearch(final RequestContext requestContext, final SearchRequest request,
396                 final IntermediateResponseHandler intermediateResponseHandler,
397                 final SearchResultHandler resultHandler) {
398             addProxiedAuthControl(request);
399             final ConnectionCompletionHandler<Result> outerHandler =
400                     new ConnectionCompletionHandler<Result>(resultHandler) {
401 
402                         @Override
403                         public void handleResult(final Connection connection) {
404                             final SearchRequestCompletionHandler innerHandler =
405                                     new SearchRequestCompletionHandler(connection, resultHandler);
406                             connection.searchAsync(request, intermediateResponseHandler,
407                                     innerHandler);
408                         }
409 
410                     };
411 
412             factory.getConnectionAsync(outerHandler);
413         }
414 
415         private void addProxiedAuthControl(final Request request) {
416             final ProxiedAuthV2RequestControl control = proxiedAuthControl;
417             if (control != null) {
418                 request.addControl(control);
419             }
420         }
421 
422     }
423 
424     /**
425      * Main method.
426      *
427      * @param args
428      *            The command line arguments: listen address, listen port,
429      *            remote address1, remote port1, remote address2, remote port2,
430      *            ...
431      */
432     public static void main(final String[] args) {
433         if (args.length < 6 || args.length % 2 != 0) {
434             System.err.println("Usage: listenAddress listenPort "
435                     + "proxyDN proxyPassword remoteAddress1 remotePort1 "
436                     + "remoteAddress2 remotePort2 ...");
437             System.exit(1);
438         }
439 
440         // Parse command line arguments.
441         final String localAddress = args[0];
442         final int localPort = Integer.parseInt(args[1]);
443 
444         final String proxyDN = args[2];
445         final String proxyPassword = args[3];
446 
447         // Create load balancer.
448         final List<ConnectionFactory> factories = new LinkedList<ConnectionFactory>();
449         final List<ConnectionFactory> bindFactories = new LinkedList<ConnectionFactory>();
450         for (int i = 4; i < args.length; i += 2) {
451             final String remoteAddress = args[i];
452             final int remotePort = Integer.parseInt(args[i + 1]);
453 
454             factories.add(Connections.newFixedConnectionPool(Connections
455                     .newAuthenticatedConnectionFactory(new LDAPConnectionFactory(remoteAddress,
456                             remotePort), Requests.newSimpleBindRequest(proxyDN, proxyPassword
457                             .toCharArray())), Integer.MAX_VALUE));
458             bindFactories.add(Connections.newFixedConnectionPool(new LDAPConnectionFactory(
459                     remoteAddress, remotePort), Integer.MAX_VALUE));
460         }
461         final RoundRobinLoadBalancingAlgorithm algorithm =
462                 new RoundRobinLoadBalancingAlgorithm(factories);
463         final RoundRobinLoadBalancingAlgorithm bindAlgorithm =
464                 new RoundRobinLoadBalancingAlgorithm(bindFactories);
465         final ConnectionFactory factory = Connections.newLoadBalancer(algorithm);
466         final ConnectionFactory bindFactory = Connections.newLoadBalancer(bindAlgorithm);
467 
468         // Create a server connection adapter.
469         final ProxyBackend backend = new ProxyBackend(factory, bindFactory);
470         final ServerConnectionFactory<LDAPClientContext, Integer> connectionHandler =
471                 Connections.newServerConnectionFactory(backend);
472 
473         // Create listener.
474         final LDAPListenerOptions options = new LDAPListenerOptions().setBacklog(4096);
475         LDAPListener listener = null;
476         try {
477             listener = new LDAPListener(localAddress, localPort, connectionHandler, options);
478             System.out.println("Press any key to stop the server...");
479             System.in.read();
480         } catch (final IOException e) {
481             System.out.println("Error listening on " + localAddress + ":" + localPort);
482             e.printStackTrace();
483         } finally {
484             if (listener != null) {
485                 listener.close();
486             }
487         }
488     }
489 
490     private Proxy() {
491         // Not used.
492     }
493 }