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 }