001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 *
019 */
020
021 package org.apache.directory.shared.dsmlv2.engine;
022
023
024 import java.io.FileNotFoundException;
025 import java.io.IOException;
026 import java.io.InputStream;
027 import java.net.InetSocketAddress;
028 import java.net.SocketAddress;
029 import java.net.UnknownHostException;
030 import java.nio.ByteBuffer;
031 import java.nio.channels.SocketChannel;
032
033 import org.apache.directory.shared.asn1.ber.Asn1Decoder;
034 import org.apache.directory.shared.asn1.ber.IAsn1Container;
035 import org.apache.directory.shared.asn1.ber.tlv.TLVStateEnum;
036 import org.apache.directory.shared.asn1.codec.DecoderException;
037 import org.apache.directory.shared.asn1.codec.EncoderException;
038 import org.apache.directory.shared.dsmlv2.Dsmlv2Parser;
039 import org.apache.directory.shared.dsmlv2.reponse.AddResponseDsml;
040 import org.apache.directory.shared.dsmlv2.reponse.AuthResponseDsml;
041 import org.apache.directory.shared.dsmlv2.reponse.BatchResponseDsml;
042 import org.apache.directory.shared.dsmlv2.reponse.CompareResponseDsml;
043 import org.apache.directory.shared.dsmlv2.reponse.DelResponseDsml;
044 import org.apache.directory.shared.dsmlv2.reponse.ErrorResponse;
045 import org.apache.directory.shared.dsmlv2.reponse.ExtendedResponseDsml;
046 import org.apache.directory.shared.dsmlv2.reponse.ModDNResponseDsml;
047 import org.apache.directory.shared.dsmlv2.reponse.ModifyResponseDsml;
048 import org.apache.directory.shared.dsmlv2.reponse.SearchResponseDsml;
049 import org.apache.directory.shared.dsmlv2.reponse.SearchResultDoneDsml;
050 import org.apache.directory.shared.dsmlv2.reponse.SearchResultEntryDsml;
051 import org.apache.directory.shared.dsmlv2.reponse.SearchResultReferenceDsml;
052 import org.apache.directory.shared.dsmlv2.reponse.ErrorResponse.ErrorResponseType;
053 import org.apache.directory.shared.dsmlv2.request.BatchRequest;
054 import org.apache.directory.shared.dsmlv2.request.BatchRequest.OnError;
055 import org.apache.directory.shared.dsmlv2.request.BatchRequest.Processing;
056 import org.apache.directory.shared.dsmlv2.request.BatchRequest.ResponseOrder;
057 import org.apache.directory.shared.i18n.I18n;
058 import org.apache.directory.shared.ldap.codec.LdapMessageCodec;
059 import org.apache.directory.shared.ldap.codec.LdapMessageContainer;
060 import org.apache.directory.shared.ldap.codec.LdapResponseCodec;
061 import org.apache.directory.shared.ldap.codec.MessageTypeEnum;
062 import org.apache.directory.shared.ldap.codec.add.AddResponseCodec;
063 import org.apache.directory.shared.ldap.codec.bind.BindRequestCodec;
064 import org.apache.directory.shared.ldap.codec.bind.BindResponseCodec;
065 import org.apache.directory.shared.ldap.codec.bind.LdapAuthentication;
066 import org.apache.directory.shared.ldap.codec.bind.SimpleAuthentication;
067 import org.apache.directory.shared.ldap.codec.compare.CompareResponseCodec;
068 import org.apache.directory.shared.ldap.codec.del.DelResponseCodec;
069 import org.apache.directory.shared.ldap.codec.extended.ExtendedResponseCodec;
070 import org.apache.directory.shared.ldap.codec.modify.ModifyResponseCodec;
071 import org.apache.directory.shared.ldap.codec.modifyDn.ModifyDNResponseCodec;
072 import org.apache.directory.shared.ldap.codec.search.SearchResultDoneCodec;
073 import org.apache.directory.shared.ldap.codec.search.SearchResultEntryCodec;
074 import org.apache.directory.shared.ldap.codec.search.SearchResultReferenceCodec;
075 import org.apache.directory.shared.ldap.exception.LdapInvalidDnException;
076 import org.apache.directory.shared.ldap.message.ResultCodeEnum;
077 import org.apache.directory.shared.ldap.message.control.Control;
078 import org.apache.directory.shared.ldap.name.DN;
079 import org.apache.directory.shared.ldap.util.StringTools;
080 import org.xmlpull.v1.XmlPullParserException;
081
082
083 /**
084 * This is the DSMLv2Engine. It can be use to execute operations on a LDAP Server and get the results of these operations.
085 * The format used for request and responses is the DSMLv2 format.
086 *
087 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
088 * @version $Rev$, $Date$
089 */
090 public class Dsmlv2Engine
091 {
092 /** Socket used to connect to the server */
093 private SocketChannel channel;
094 private SocketAddress serverAddress;
095
096 // server configuration
097 private int port;
098 private String host;
099 private String user;
100 private String password;
101
102 private Asn1Decoder ldapDecoder = new Asn1Decoder();
103
104 private IAsn1Container ldapMessageContainer = new LdapMessageContainer();
105
106 private Dsmlv2Parser parser;
107
108 private boolean continueOnError;
109 private boolean exit = false;
110
111 private int bbLimit;
112
113 private int bbposition;
114 private BatchRequest batchRequest;
115 private BatchResponseDsml batchResponse;
116
117
118 /**
119 * Creates a new instance of Dsmlv2Engine.
120 *
121 * @param host
122 * the server host
123 * @param port
124 * the server port
125 * @param user
126 * the server admin DN
127 * @param password
128 * the server admin's password
129 */
130 public Dsmlv2Engine( String host, int port, String user, String password )
131 {
132 this.host = host;
133 this.port = port;
134 this.user = user;
135 this.password = password;
136 }
137
138
139 /**
140 * Processes the file given and return the result of the operations
141 *
142 * @param dsmlInput
143 * the DSMLv2 formatted request input
144 * @return
145 * the XML response in DSMLv2 Format
146 * @throws XmlPullParserException
147 * if an error occurs in the parser
148 */
149 public String processDSML( String dsmlInput ) throws XmlPullParserException
150 {
151 parser = new Dsmlv2Parser();
152 parser.setInput( dsmlInput );
153 return processDSML();
154 }
155
156
157 /**
158 * Processes the file given and return the result of the operations
159 *
160 * @param fileName
161 * the path to the file
162 * @return
163 * the XML response in DSMLv2 Format
164 * @throws XmlPullParserException
165 * if an error occurs in the parser
166 * @throws FileNotFoundException
167 * if the file does not exist
168 */
169 public String processDSMLFile( String fileName ) throws XmlPullParserException, FileNotFoundException
170 {
171 parser = new Dsmlv2Parser();
172 parser.setInputFile( fileName );
173 return processDSML();
174 }
175
176
177 /**
178 * Processes the file given and return the result of the operations
179 *
180 * @param inputStream
181 * contains a raw byte input stream of possibly unknown encoding (when inputEncoding is null).
182 * @param inputEncoding
183 * if not null it MUST be used as encoding for inputStream
184 * @return
185 * the XML response in DSMLv2 Format
186 * @throws XmlPullParserException
187 * if an error occurs in the parser
188 */
189 public String processDSML( InputStream inputStream, String inputEncoding ) throws XmlPullParserException
190 {
191 parser = new Dsmlv2Parser();
192 parser.setInput( inputStream, inputEncoding );
193 return processDSML();
194 }
195
196
197 /**
198 * Processes the Request document
199 *
200 * @return
201 * the XML response in DSMLv2 Format
202 */
203 private String processDSML()
204 {
205 batchResponse = new BatchResponseDsml();
206
207 // Binding to LDAP Server
208 try
209 {
210 bind( 1 );
211 }
212 catch ( Exception e )
213 {
214 // Unable to connect to server
215 // We create a new ErrorResponse and return the XML response.
216 ErrorResponse errorResponse = new ErrorResponse( 0, ErrorResponseType.COULD_NOT_CONNECT, e.getLocalizedMessage() );
217 batchResponse.addResponse( errorResponse );
218 return batchResponse.toDsml();
219 }
220
221 // Processing BatchRequest:
222 // - Parsing and Getting BatchRequest
223 // - Getting and registering options from BatchRequest
224 try
225 {
226 processBatchRequest();
227 }
228 catch ( XmlPullParserException e )
229 {
230 // We create a new ErrorResponse and return the XML response.
231 ErrorResponse errorResponse = new ErrorResponse( 0, ErrorResponseType.MALFORMED_REQUEST, I18n.err(I18n.ERR_03001, e.getLocalizedMessage(),
232 e.getLineNumber(), e.getColumnNumber() ) );
233 batchResponse.addResponse( errorResponse );
234 return batchResponse.toDsml();
235 }
236
237 // Processing each request:
238 // - Getting a new request
239 // - Checking if the request is well formed
240 // - Sending the request to the server
241 // - Getting and converting reponse(s) as XML
242 // - Looping until last request
243 LdapMessageCodec request = null;
244 try
245 {
246 request = parser.getNextRequest();
247 }
248 catch ( XmlPullParserException e )
249 {
250 // We create a new ErrorResponse and return the XML response.
251 ErrorResponse errorResponse = new ErrorResponse( 0, ErrorResponseType.MALFORMED_REQUEST, I18n.err(I18n.ERR_03001, e.getLocalizedMessage(),
252 e.getLineNumber(), e.getColumnNumber() ) );
253 batchResponse.addResponse( errorResponse );
254 return batchResponse.toDsml();
255 }
256
257 while ( request != null ) // (Request == null when there's no more request to process)
258 {
259 // Checking the request has a requestID attribute if Processing = Parallel and ResponseOrder = Unordered
260 if ( ( batchRequest.getProcessing().equals( Processing.PARALLEL ) )
261 && ( batchRequest.getResponseOrder().equals( ResponseOrder.UNORDERED ) )
262 && ( request.getMessageId() == 0 ) )
263 {
264 // Then we have to send an errorResponse
265 ErrorResponse errorResponse = new ErrorResponse( 0, ErrorResponseType.MALFORMED_REQUEST, I18n.err( I18n.ERR_03002 ) );
266 batchResponse.addResponse( errorResponse );
267 return batchResponse.toDsml();
268 }
269
270 try
271 {
272 processRequest( request );
273 }
274 catch ( Exception e )
275 {
276 // We create a new ErrorResponse and return the XML response.
277 ErrorResponse errorResponse = new ErrorResponse( 0, ErrorResponseType.GATEWAY_INTERNAL_ERROR,
278 I18n.err( I18n.ERR_03003, e.getMessage() ) );
279 batchResponse.addResponse( errorResponse );
280 return batchResponse.toDsml();
281 }
282
283 // Checking if we need to exit processing (if an error has occurred if onError == Exit)
284 if ( exit )
285 {
286 break;
287 }
288
289 // Getting next request
290 try
291 {
292 request = parser.getNextRequest();
293 }
294 catch ( XmlPullParserException e )
295 {
296 // We create a new ErrorResponse and return the XML response.
297 ErrorResponse errorResponse = new ErrorResponse( 0, ErrorResponseType.MALFORMED_REQUEST, I18n.err( I18n.ERR_03001,
298 e.getLocalizedMessage(), e.getLineNumber(), e.getColumnNumber() ) );
299 batchResponse.addResponse( errorResponse );
300 return batchResponse.toDsml();
301 }
302 }
303
304 return batchResponse.toDsml();
305 }
306
307
308 /**
309 * Processes a single request
310 *
311 * @param request
312 * the request to process
313 * @throws EncoderException
314 * @throws IOException
315 * @throws DecoderException
316 */
317 private void processRequest( LdapMessageCodec request ) throws EncoderException, IOException, DecoderException
318 {
319 ByteBuffer bb = request.encode();
320
321 bb.flip();
322
323 sendMessage( bb );
324
325 bb.clear();
326 bb.position( bb.capacity() );
327
328 // Get the response
329 LdapMessageCodec response = null;
330
331 response = readResponse( bb );
332
333 switch ( response.getMessageType() )
334 {
335 case ADD_RESPONSE :
336 AddResponseCodec addResponse = (AddResponseCodec)response;
337 copyMessageIdAndControls( response, addResponse );
338
339 AddResponseDsml addResponseDsml = new AddResponseDsml( addResponse );
340 batchResponse.addResponse( addResponseDsml );
341 break;
342
343 case BIND_RESPONSE :
344 BindResponseCodec bindResponse = (BindResponseCodec)response;
345 copyMessageIdAndControls( response, bindResponse );
346
347 AuthResponseDsml authResponseDsml = new AuthResponseDsml( bindResponse );
348 batchResponse.addResponse( authResponseDsml );
349 break;
350
351 case COMPARE_RESPONSE :
352 CompareResponseCodec compareResponse = (CompareResponseCodec)response;
353 copyMessageIdAndControls( response, compareResponse );
354
355 CompareResponseDsml compareResponseDsml = new CompareResponseDsml( compareResponse );
356 batchResponse.addResponse( compareResponseDsml );
357 break;
358
359 case DEL_RESPONSE :
360 DelResponseCodec delResponse = (DelResponseCodec)response;
361 copyMessageIdAndControls( response, delResponse );
362
363 DelResponseDsml delResponseDsml = new DelResponseDsml( delResponse );
364 batchResponse.addResponse( delResponseDsml );
365 break;
366
367 case MODIFY_RESPONSE :
368 ModifyResponseCodec modifyResponse = (ModifyResponseCodec)response;
369 copyMessageIdAndControls( response, modifyResponse );
370
371 ModifyResponseDsml modifyResponseDsml = new ModifyResponseDsml( modifyResponse );
372 batchResponse.addResponse( modifyResponseDsml );
373 break;
374
375 case MODIFYDN_RESPONSE :
376 ModifyDNResponseCodec modifyDNResponse = (ModifyDNResponseCodec)response;
377 copyMessageIdAndControls( response, modifyDNResponse );
378
379 ModDNResponseDsml modDNResponseDsml = new ModDNResponseDsml( modifyDNResponse );
380 batchResponse.addResponse( modDNResponseDsml );
381 break;
382
383 case EXTENDED_RESPONSE :
384 ExtendedResponseCodec extendedResponse = (ExtendedResponseCodec)response;
385 copyMessageIdAndControls( response, extendedResponse );
386
387 ExtendedResponseDsml extendedResponseDsml = new ExtendedResponseDsml( extendedResponse );
388 batchResponse.addResponse( extendedResponseDsml );
389 break;
390
391 case SEARCH_RESULT_ENTRY :
392 case SEARCH_RESULT_REFERENCE :
393 case SEARCH_RESULT_DONE :
394 // A SearchResponse can contains multiple responses of 3 types:
395 // - 0 to n SearchResultEntry
396 // - O to n SearchResultReference
397 // - 1 (only) SearchResultDone
398 // So we have to include those individual responses in a "General" SearchResponse
399 SearchResponseDsml searchResponseDsml = null;
400
401 // RequestID
402 int requestID = response.getMessageId();
403
404 while ( MessageTypeEnum.SEARCH_RESULT_DONE != response.getMessageType() )
405 {
406 if ( MessageTypeEnum.SEARCH_RESULT_ENTRY == response.getMessageType() )
407 {
408 SearchResultEntryCodec sre = (SearchResultEntryCodec)response;
409 copyMessageIdAndControls( response, sre );
410
411 SearchResultEntryDsml searchResultEntryDsml = new SearchResultEntryDsml( sre );
412 searchResponseDsml = new SearchResponseDsml( (LdapMessageCodec)sre );
413
414 if ( requestID != 0 )
415 {
416 searchResponseDsml.setMessageId( requestID );
417 }
418
419 searchResponseDsml.addResponse( searchResultEntryDsml );
420 }
421 else if ( MessageTypeEnum.SEARCH_RESULT_REFERENCE == response.getMessageType() )
422 {
423 SearchResultReferenceCodec srr = (SearchResultReferenceCodec)response;
424 copyMessageIdAndControls( response, srr );
425
426 SearchResultReferenceDsml searchResultReferenceDsml = new SearchResultReferenceDsml( srr );
427 searchResponseDsml.addResponse( searchResultReferenceDsml );
428 }
429
430 response = readResponse( bb );
431 }
432
433 SearchResultDoneCodec srd = (SearchResultDoneCodec)response;
434 copyMessageIdAndControls( response, srd );
435
436 SearchResultDoneDsml searchResultDoneDsml = new SearchResultDoneDsml( srd );
437 searchResponseDsml.addResponse( searchResultDoneDsml );
438 break;
439 }
440
441 LdapResponseCodec realResponse = (LdapResponseCodec)response;
442
443 if ( !continueOnError )
444 {
445 if ( ( realResponse.getLdapResult().getResultCode() != ResultCodeEnum.SUCCESS )
446 && ( realResponse.getLdapResult().getResultCode() != ResultCodeEnum.COMPARE_TRUE )
447 && ( realResponse.getLdapResult().getResultCode() != ResultCodeEnum.COMPARE_FALSE )
448 && ( realResponse.getLdapResult().getResultCode() != ResultCodeEnum.REFERRAL ) )
449 {
450 // Turning on Exit flag
451 exit = true;
452 }
453 }
454 }
455
456
457 private void copyMessageIdAndControls( LdapMessageCodec from, LdapMessageCodec to )
458 {
459 to.setMessageId( from.getMessageId() );
460
461 for ( Control control : from.getControls() )
462 {
463 to.addControl( control );
464 }
465 }
466
467
468 /**
469 * Processes the BatchRequest
470 * <ul>
471 * <li>Parsing and Getting BatchRequest</li>
472 * <li>Getting and registering options from BatchRequest</li>
473 * </ul>
474 *
475 * @throws XmlPullParserException
476 * if an error occurs in the parser
477 */
478 private void processBatchRequest() throws XmlPullParserException
479 {
480 // Parsing BatchRequest
481 parser.parseBatchRequest();
482
483 // Getting BatchRequest
484 batchRequest = parser.getBatchRequest();
485
486 if ( OnError.RESUME.equals( batchRequest.getOnError() ) )
487 {
488 continueOnError = true;
489 }
490 else if ( OnError.EXIT.equals( batchRequest.getOnError() ) )
491 {
492 continueOnError = false;
493 }
494
495 if ( batchRequest.getRequestID() != 0 )
496 {
497 batchResponse.setRequestID( batchRequest.getRequestID() );
498 }
499 }
500
501
502 /**
503 * Connect to the LDAP server through a socket and establish the Input and
504 * Output Streams. All the required information for the connection should be
505 * in the options from the command line, or the default values.
506 *
507 * @throws UnknownHostException
508 * if the hostname or the Address of server could not be found
509 * @throws IOException
510 * if there was a error opening or establishing the socket
511 */
512 private void connect() throws UnknownHostException, IOException
513 {
514 serverAddress = new InetSocketAddress( host, port );
515 channel = SocketChannel.open( serverAddress );
516 channel.configureBlocking( true );
517 }
518
519
520 /**
521 * Sends a message
522 *
523 * @param bb
524 * the message as a byte buffer
525 * @throws IOException
526 * if the message could not be sent
527 */
528 private void sendMessage( ByteBuffer bb ) throws IOException
529 {
530 channel.write( bb );
531 bb.clear();
532 }
533
534
535 /**
536 * Reads the response to a request
537 *
538 * @param bb
539 * the response as a byte buffer
540 * @return the response
541 * the response as a LDAP message
542 * @throws IOException
543 * @throws DecoderException
544 */
545 private LdapMessageCodec readResponse( ByteBuffer bb ) throws IOException, DecoderException
546 {
547
548 LdapMessageCodec messageResp = null;
549
550 if ( bb.hasRemaining() )
551 {
552 bb.position( bbposition );
553 bb.limit( bbLimit );
554 ldapDecoder.decode( bb, ldapMessageContainer );
555 bbposition = bb.position();
556 bbLimit = bb.limit();
557 }
558 bb.flip();
559 while ( ldapMessageContainer.getState() != TLVStateEnum.PDU_DECODED )
560 {
561
562 int nbRead = channel.read( bb );
563
564 if ( nbRead == -1 )
565 {
566 System.err.println( "fsdfsdfsdfsd" );
567 }
568
569 bb.flip();
570 ldapDecoder.decode( bb, ldapMessageContainer );
571 bbposition = bb.position();
572 bbLimit = bb.limit();
573 bb.flip();
574 }
575
576 messageResp = ( ( LdapMessageContainer ) ldapMessageContainer ).getLdapMessage();
577
578 if ( messageResp instanceof BindResponseCodec )
579 {
580 BindResponseCodec resp = ( ( LdapMessageContainer ) ldapMessageContainer ).getBindResponse();
581
582 if ( resp.getLdapResult().getResultCode() != ResultCodeEnum.SUCCESS )
583 {
584 System.err.println( "Error : " + resp.getLdapResult().getErrorMessage() );
585 }
586 }
587 else if ( messageResp instanceof ExtendedResponseCodec )
588 {
589 ExtendedResponseCodec resp = ( ( LdapMessageContainer ) ldapMessageContainer ).getExtendedResponse();
590
591 if ( resp.getLdapResult().getResultCode() != ResultCodeEnum.SUCCESS )
592 {
593 System.err.println( "Error : " + resp.getLdapResult().getErrorMessage() );
594 }
595 }
596
597 ( ( LdapMessageContainer ) ldapMessageContainer ).clean();
598
599 return messageResp;
600 }
601
602
603 /**
604 * Binds to the ldap server
605 *
606 * @param messageId
607 * the message Id
608 * @throws EncoderException
609 * @throws DecoderException
610 * @throws IOException
611 * @throws LdapInvalidDnException
612 */
613 private void bind( int messageId ) throws EncoderException, DecoderException, IOException, LdapInvalidDnException
614 {
615 BindRequestCodec bindRequest = new BindRequestCodec();
616 LdapAuthentication authentication = new SimpleAuthentication();
617 ( ( SimpleAuthentication ) authentication ).setSimple( StringTools.getBytesUtf8( password ) );
618
619 bindRequest.setAuthentication( authentication );
620 bindRequest.setName( new DN( user ) );
621 bindRequest.setVersion( 3 );
622
623 bindRequest.setMessageId( messageId );
624
625 // Encode and send the bind request
626 ByteBuffer bb = bindRequest.encode();
627 bb.flip();
628
629 connect();
630 sendMessage( bb );
631
632 bb.clear();
633
634 bb.position( bb.limit() );
635
636 readResponse( bb );
637 }
638 }