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    }