1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.forgerock.opendj.examples;
29
30 import static org.forgerock.opendj.ldap.ErrorResultException.newErrorResult;
31
32 import java.io.FileInputStream;
33 import java.io.FileNotFoundException;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.util.NavigableMap;
37 import java.util.concurrent.ConcurrentSkipListMap;
38 import java.util.concurrent.locks.ReentrantReadWriteLock;
39
40 import javax.net.ssl.SSLContext;
41
42 import org.forgerock.opendj.ldap.CancelledResultException;
43 import org.forgerock.opendj.ldap.Connections;
44 import org.forgerock.opendj.ldap.DN;
45 import org.forgerock.opendj.ldap.Entry;
46 import org.forgerock.opendj.ldap.ErrorResultException;
47 import org.forgerock.opendj.ldap.Filter;
48 import org.forgerock.opendj.ldap.IntermediateResponseHandler;
49 import org.forgerock.opendj.ldap.KeyManagers;
50 import org.forgerock.opendj.ldap.LDAPClientContext;
51 import org.forgerock.opendj.ldap.LDAPListener;
52 import org.forgerock.opendj.ldap.LDAPListenerOptions;
53 import org.forgerock.opendj.ldap.LinkedHashMapEntry;
54 import org.forgerock.opendj.ldap.Matcher;
55 import org.forgerock.opendj.ldap.Modification;
56 import org.forgerock.opendj.ldap.ModificationType;
57 import org.forgerock.opendj.ldap.RequestContext;
58 import org.forgerock.opendj.ldap.RequestHandler;
59 import org.forgerock.opendj.ldap.ResultCode;
60 import org.forgerock.opendj.ldap.ResultHandler;
61 import org.forgerock.opendj.ldap.SSLContextBuilder;
62 import org.forgerock.opendj.ldap.SearchResultHandler;
63 import org.forgerock.opendj.ldap.SearchScope;
64 import org.forgerock.opendj.ldap.ServerConnection;
65 import org.forgerock.opendj.ldap.ServerConnectionFactory;
66 import org.forgerock.opendj.ldap.TrustManagers;
67 import org.forgerock.opendj.ldap.requests.AddRequest;
68 import org.forgerock.opendj.ldap.requests.BindRequest;
69 import org.forgerock.opendj.ldap.requests.CompareRequest;
70 import org.forgerock.opendj.ldap.requests.DeleteRequest;
71 import org.forgerock.opendj.ldap.requests.ExtendedRequest;
72 import org.forgerock.opendj.ldap.requests.ModifyDNRequest;
73 import org.forgerock.opendj.ldap.requests.ModifyRequest;
74 import org.forgerock.opendj.ldap.requests.SearchRequest;
75 import org.forgerock.opendj.ldap.responses.BindResult;
76 import org.forgerock.opendj.ldap.responses.CompareResult;
77 import org.forgerock.opendj.ldap.responses.ExtendedResult;
78 import org.forgerock.opendj.ldap.responses.Responses;
79 import org.forgerock.opendj.ldap.responses.Result;
80 import org.forgerock.opendj.ldif.LDIFEntryReader;
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97 public final class Server {
98 private static final class MemoryBackend implements RequestHandler<RequestContext> {
99 private final ConcurrentSkipListMap<DN, Entry> entries;
100 private final ReentrantReadWriteLock entryLock = new ReentrantReadWriteLock();
101
102 private MemoryBackend(final ConcurrentSkipListMap<DN, Entry> entries) {
103 this.entries = entries;
104 }
105
106
107
108
109 @Override
110 public void handleAdd(final RequestContext requestContext, final AddRequest request,
111 final IntermediateResponseHandler intermediateResponseHandler,
112 final ResultHandler<? super Result> resultHandler) {
113
114 entryLock.writeLock().lock();
115 try {
116 DN dn = request.getName();
117 if (entries.containsKey(dn)) {
118 resultHandler.handleErrorResult(ErrorResultException.newErrorResult(
119 ResultCode.ENTRY_ALREADY_EXISTS, "The entry " + dn.toString()
120 + " already exists"));
121 }
122
123 DN parent = dn.parent();
124 if (!entries.containsKey(parent)) {
125 resultHandler.handleErrorResult(ErrorResultException.newErrorResult(
126 ResultCode.NO_SUCH_OBJECT, "The parent entry " + parent.toString()
127 + " does not exist"));
128 } else {
129 entries.put(dn, request);
130 resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
131 }
132 } finally {
133 entryLock.writeLock().unlock();
134 }
135 }
136
137
138
139
140 @Override
141 public void handleBind(final RequestContext requestContext, final int version,
142 final BindRequest request,
143 final IntermediateResponseHandler intermediateResponseHandler,
144 final ResultHandler<? super BindResult> resultHandler) {
145 if (request.getAuthenticationType() != ((byte) 0x80)) {
146
147 resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR,
148 "non-SIMPLE authentication not supported: "
149 + request.getAuthenticationType()));
150 } else {
151
152 resultHandler.handleResult(Responses.newBindResult(ResultCode.SUCCESS));
153 }
154 }
155
156
157
158
159 @Override
160 public void handleCompare(final RequestContext requestContext,
161 final CompareRequest request,
162 final IntermediateResponseHandler intermediateResponseHandler,
163 final ResultHandler<? super CompareResult> resultHandler) {
164
165 }
166
167
168
169
170 @Override
171 public void handleDelete(final RequestContext requestContext, final DeleteRequest request,
172 final IntermediateResponseHandler intermediateResponseHandler,
173 final ResultHandler<? super Result> resultHandler) {
174
175 entryLock.writeLock().lock();
176 try {
177
178 DN dn = request.getName();
179 if (!entries.containsKey(dn)) {
180 resultHandler.handleErrorResult(ErrorResultException.newErrorResult(
181 ResultCode.NO_SUCH_OBJECT, "The entry " + dn.toString()
182 + " does not exist"));
183 } else {
184 entries.remove(dn);
185 resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
186 }
187 } finally {
188 entryLock.writeLock().unlock();
189 }
190 }
191
192
193
194
195 @Override
196 public <R extends ExtendedResult> void handleExtendedRequest(
197 final RequestContext requestContext, final ExtendedRequest<R> request,
198 final IntermediateResponseHandler intermediateResponseHandler,
199 final ResultHandler<? super R> resultHandler) {
200
201 resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR,
202 "Extended request operation not supported"));
203 }
204
205
206
207
208 @Override
209 public void handleModify(final RequestContext requestContext, final ModifyRequest request,
210 final IntermediateResponseHandler intermediateResponseHandler,
211 final ResultHandler<? super Result> resultHandler) {
212
213
214
215 entryLock.readLock().lock();
216 try {
217 DN dn = request.getName();
218 Entry entry = entries.get(dn);
219 if (entry == null) {
220 resultHandler.handleErrorResult(ErrorResultException.newErrorResult(
221 ResultCode.NO_SUCH_OBJECT, "The entry " + dn.toString()
222 + " does not exist"));
223 }
224
225 Entry newEntry = new LinkedHashMapEntry(entry);
226 for (Modification mod : request.getModifications()) {
227 ModificationType modType = mod.getModificationType();
228 if (modType.equals(ModificationType.ADD)) {
229
230 newEntry.addAttribute(mod.getAttribute(), null);
231 } else if (modType.equals(ModificationType.DELETE)) {
232
233 newEntry.removeAttribute(mod.getAttribute(), null);
234 } else if (modType.equals(ModificationType.REPLACE)) {
235 newEntry.replaceAttribute(mod.getAttribute());
236 } else {
237 resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR,
238 "Modify request contains an unsupported modification type"));
239 return;
240 }
241 }
242
243 entries.put(dn, newEntry);
244 resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
245 } finally {
246 entryLock.readLock().unlock();
247 }
248 }
249
250
251
252
253 @Override
254 public void handleModifyDN(final RequestContext requestContext,
255 final ModifyDNRequest request,
256 final IntermediateResponseHandler intermediateResponseHandler,
257 final ResultHandler<? super Result> resultHandler) {
258
259 resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR,
260 "ModifyDN request operation not supported"));
261 }
262
263
264
265
266 @Override
267 public void handleSearch(final RequestContext requestContext, final SearchRequest request,
268 final IntermediateResponseHandler intermediateResponseHandler,
269 final SearchResultHandler resultHandler) {
270
271 entryLock.readLock().lock();
272 try {
273 DN dn = request.getName();
274 Entry baseEntry = entries.get(dn);
275 if (baseEntry == null) {
276 resultHandler.handleErrorResult(ErrorResultException.newErrorResult(
277 ResultCode.NO_SUCH_OBJECT, "The entry " + dn.toString()
278 + " does not exist"));
279 return;
280 }
281
282 SearchScope scope = request.getScope();
283 Filter filter = request.getFilter();
284 Matcher matcher = filter.matcher();
285
286 if (scope.equals(SearchScope.BASE_OBJECT)) {
287 if (matcher.matches(baseEntry).toBoolean()) {
288 sendEntry(request, resultHandler, baseEntry);
289 }
290 } else if (scope.equals(SearchScope.SINGLE_LEVEL)) {
291 NavigableMap<DN, Entry> subtree = entries.tailMap(dn, false);
292 for (Entry entry : subtree.values()) {
293
294 requestContext.checkIfCancelled(false);
295
296 DN childDN = entry.getName();
297 if (childDN.isChildOf(dn)) {
298 if (!matcher.matches(entry).toBoolean()) {
299 continue;
300 }
301
302 if (!sendEntry(request, resultHandler, entry)) {
303
304 break;
305 }
306 } else if (!childDN.isSubordinateOrEqualTo(dn)) {
307
308 break;
309 }
310 }
311 } else if (scope.equals(SearchScope.WHOLE_SUBTREE)) {
312 NavigableMap<DN, Entry> subtree = entries.tailMap(dn);
313 for (Entry entry : subtree.values()) {
314
315 requestContext.checkIfCancelled(false);
316
317 DN childDN = entry.getName();
318 if (childDN.isSubordinateOrEqualTo(dn)) {
319 if (!matcher.matches(entry).toBoolean()) {
320 continue;
321 }
322
323 if (!sendEntry(request, resultHandler, entry)) {
324
325 break;
326 }
327 } else {
328
329 break;
330 }
331 }
332 } else {
333 resultHandler.handleErrorResult(newErrorResult(ResultCode.PROTOCOL_ERROR,
334 "Search request contains an unsupported search scope"));
335 return;
336 }
337
338 resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
339 } catch (CancelledResultException e) {
340 resultHandler.handleErrorResult(e);
341 } finally {
342 entryLock.readLock().unlock();
343 }
344 }
345
346 private boolean sendEntry(SearchRequest request, SearchResultHandler resultHandler,
347 Entry entry) {
348
349 return resultHandler.handleEntry(Responses.newSearchResultEntry(entry));
350 }
351 }
352
353
354
355
356
357
358
359
360 public static void main(final String[] args) {
361 if (args.length != 3 && args.length != 6) {
362 System.err.println("Usage: listenAddress listenPort ldifFile "
363 + "[keyStoreFile keyStorePassword certNickname]");
364 System.exit(1);
365 }
366
367
368 final String localAddress = args[0];
369 final int localPort = Integer.parseInt(args[1]);
370 final String ldifFileName = args[2];
371 final String keyStoreFileName = (args.length == 6) ? args[3] : null;
372 final String keyStorePassword = (args.length == 6) ? args[4] : null;
373 final String certNickname = (args.length == 6) ? args[5] : null;
374
375
376 final ConcurrentSkipListMap<DN, Entry> entries = readEntriesFromLDIF(ldifFileName);
377 final MemoryBackend backend = new MemoryBackend(entries);
378
379
380 final ServerConnectionFactory<LDAPClientContext, Integer> connectionHandler =
381 Connections.newServerConnectionFactory(backend);
382
383
384 LDAPListener listener = null;
385 try {
386 final LDAPListenerOptions options = new LDAPListenerOptions().setBacklog(4096);
387
388 if (keyStoreFileName != null) {
389
390
391 final SSLContext sslContext =
392 new SSLContextBuilder().setKeyManager(
393 KeyManagers.useSingleCertificate(certNickname, KeyManagers
394 .useKeyStoreFile(keyStoreFileName, keyStorePassword
395 .toCharArray(), null))).setTrustManager(
396 TrustManagers.trustAll()).getSSLContext();
397
398 ServerConnectionFactory<LDAPClientContext, Integer> sslWrapper =
399 new ServerConnectionFactory<LDAPClientContext, Integer>() {
400
401 public ServerConnection<Integer> handleAccept(
402 LDAPClientContext clientContext) throws ErrorResultException {
403 clientContext.enableTLS(sslContext, null, null, false, false);
404 return connectionHandler.handleAccept(clientContext);
405 }
406 };
407
408 listener = new LDAPListener(localAddress, localPort, sslWrapper, options);
409 } else {
410
411 listener = new LDAPListener(localAddress, localPort, connectionHandler, options);
412 }
413 System.out.println("Press any key to stop the server...");
414 System.in.read();
415 } catch (final Exception e) {
416 System.out.println("Error listening on " + localAddress + ":" + localPort);
417 e.printStackTrace();
418 } finally {
419 if (listener != null) {
420 listener.close();
421 }
422 }
423 }
424
425
426
427
428
429
430
431
432 private static ConcurrentSkipListMap<DN, Entry> readEntriesFromLDIF(final String ldifFileName) {
433 final ConcurrentSkipListMap<DN, Entry> entries;
434
435 InputStream ldif;
436 try {
437 ldif = new FileInputStream(ldifFileName);
438 } catch (final FileNotFoundException e) {
439 System.err.println(e.getMessage());
440 System.exit(ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue());
441 return null;
442 }
443
444 entries = new ConcurrentSkipListMap<DN, Entry>();
445 final LDIFEntryReader reader = new LDIFEntryReader(ldif);
446 try {
447 while (reader.hasNext()) {
448 Entry entry = reader.readEntry();
449 entries.put(entry.getName(), entry);
450 }
451 } catch (final IOException e) {
452 System.err.println(e.getMessage());
453 System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
454 return null;
455 } finally {
456 try {
457 reader.close();
458 } catch (final IOException ignored) {
459
460 }
461 }
462
463
464
465 boolean isValid = true;
466 for (DN dn : entries.keySet()) {
467 if (dn.size() > 1) {
468 DN parent = dn.parent();
469 if (!entries.containsKey(parent)) {
470 System.err
471 .println("The entry \"" + dn.toString() + "\" does not have a parent");
472 isValid = false;
473 }
474 }
475 }
476 if (!isValid) {
477 System.exit(1);
478 }
479 return entries;
480 }
481
482 private Server() {
483
484 }
485 }