001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2009 Sun Microsystems, Inc. 025 * Portions Copyright 2012-2015 ForgeRock AS. 026 */ 027package org.opends.dsml.protocol; 028 029 030 031import java.io.IOException; 032import java.util.ArrayList; 033import java.util.LinkedHashSet; 034import java.util.LinkedList; 035import java.util.List; 036 037import javax.xml.bind.JAXBElement; 038 039import org.forgerock.i18n.LocalizableMessage; 040import org.forgerock.opendj.ldap.DecodeException; 041import org.opends.server.protocols.ldap.LDAPAttribute; 042import org.opends.server.protocols.ldap.LDAPConstants; 043import org.opends.server.protocols.ldap.LDAPFilter; 044import org.opends.server.protocols.ldap.LDAPMessage; 045import org.opends.server.protocols.ldap.LDAPResultCode; 046import org.opends.server.protocols.ldap.SearchRequestProtocolOp; 047import org.opends.server.protocols.ldap.SearchResultDoneProtocolOp; 048import org.opends.server.protocols.ldap.SearchResultEntryProtocolOp; 049import org.opends.server.tools.LDAPConnection; 050import org.forgerock.opendj.ldap.ByteString; 051import org.forgerock.opendj.ldap.DereferenceAliasesPolicy; 052import org.opends.server.types.LDAPException; 053import org.opends.server.types.RawFilter; 054import org.forgerock.opendj.ldap.SearchScope; 055import static org.opends.messages.ProtocolMessages.*; 056 057 058 059/** 060 * This class provides the functionality for the performing an LDAP 061 * SEARCH operation based on the specified DSML request. 062 */ 063public class DSMLSearchOperation 064{ 065 066 private LDAPConnection connection; 067 068 069 070 /** 071 * Create the instance with the specified connection. 072 * 073 * @param connection 074 * The LDAP connection to send the request on. 075 */ 076 077 public DSMLSearchOperation(LDAPConnection connection) 078 { 079 this.connection = connection; 080 } 081 082 083 084 /** 085 * Returns a new AND search filter with the provided filter 086 * components. 087 * 088 * @param filterSet 089 * The filter components for this filter 090 * @return a new AND search filter with the provided filter 091 * components. 092 * @throws LDAPException 093 * an LDAPException is thrown if the creation of a filter 094 * component fails. 095 * @throws IOException if a value is an anyURI and cannot be fetched. 096 */ 097 private static LDAPFilter createANDFilter(FilterSet filterSet) 098 throws LDAPException, IOException 099 { 100 List<JAXBElement<?>> list = filterSet.getFilterGroup(); 101 ArrayList<RawFilter> filters = new ArrayList<>(list.size()); 102 103 for (JAXBElement<?> filter : list) 104 { 105 filters.add(createFilter(filter)); 106 } 107 return LDAPFilter.createANDFilter(filters); 108 } 109 110 111 112 /** 113 * Returns a new Approximate search filter with the provided 114 * information. 115 * 116 * @param ava 117 * the attribute value assertion for this approximate 118 * filter. 119 * @return a new Approximate search filter with the provided 120 * information. 121 * @throws IOException if a value is an anyURI and cannot be fetched. 122 */ 123 private static LDAPFilter createApproximateFilter(AttributeValueAssertion ava) 124 throws IOException 125 { 126 return LDAPFilter.createApproximateFilter(ava.getName(), 127 ByteStringUtility.convertValue(ava.getValue())); 128 } 129 130 131 132 /** 133 * Returns a new Equality search filter with the provided 134 * information. 135 * 136 * @param ava 137 * the attribute value assertion for this Equality filter. 138 * @return a new Equality search filter with the provided 139 * information. 140 * @throws IOException if a value is an anyURI and cannot be fetched. 141 */ 142 private static LDAPFilter createEqualityFilter(AttributeValueAssertion ava) 143 throws IOException 144 { 145 return LDAPFilter.createEqualityFilter(ava.getName(), 146 ByteStringUtility.convertValue(ava.getValue())); 147 } 148 149 150 151 /** 152 * Returns a new Extensible search filter with the provided 153 * information. 154 * 155 * @param mra 156 * the matching rule assertion for this Extensible filter. 157 * @return a new Extensible search filter with the provided 158 * information. 159 * @throws IOException if a value is an anyURI and cannot be fetched. 160 */ 161 private static LDAPFilter createExtensibleFilter(MatchingRuleAssertion mra) 162 throws IOException 163 { 164 return LDAPFilter.createExtensibleFilter(mra.getMatchingRule(), mra 165 .getName(), ByteStringUtility.convertValue(mra.getValue()), 166 mra.isDnAttributes()); 167 } 168 169 170 171 /** 172 * Returns a new GreaterOrEqual search filter with the provided 173 * information. 174 * 175 * @param ava 176 * the attribute value assertion for this GreaterOrEqual 177 * filter. 178 * @return a new GreaterOrEqual search filter with the provided 179 * information. 180 * @throws IOException if a value is an anyURI and cannot be fetched. 181 */ 182 private static LDAPFilter createGreaterOrEqualFilter( 183 AttributeValueAssertion ava) 184 throws IOException 185 { 186 return LDAPFilter.createGreaterOrEqualFilter(ava.getName(), 187 ByteStringUtility.convertValue(ava.getValue())); 188 } 189 190 191 192 /** 193 * Returns a new LessOrEqual search filter with the provided 194 * information. 195 * 196 * @param ava 197 * the attribute value assertion for this LessOrEqual 198 * filter. 199 * @return a new LessOrEqual search filter with the provided 200 * information. 201 * @throws IOException if a value is an anyURI and cannot be fetched. 202 */ 203 private static LDAPFilter createLessOrEqualFilter(AttributeValueAssertion ava) 204 throws IOException 205 { 206 return LDAPFilter.createLessOrEqualFilter(ava.getName(), 207 ByteStringUtility.convertValue(ava.getValue())); 208 } 209 210 211 212 /** 213 * Returns a new NOT search filter with the provided information. 214 * 215 * @param filter 216 * the filter for this NOT filter. 217 * @return a new NOT search filter with the provided information. 218 * @throws LDAPException 219 * an LDAPException is thrown if the creation of the 220 * provided filter fails. 221 * @throws IOException if a value is an anyURI and cannot be fetched. 222 */ 223 private static LDAPFilter createNOTFilter(Filter filter) 224 throws LDAPException, IOException 225 { 226 return LDAPFilter.createNOTFilter(createFilter(filter)); 227 } 228 229 230 231 /** 232 * Returns a new OR search filter with the provided filter 233 * components. 234 * 235 * @param filterSet 236 * The filter components for this filter 237 * @return a new OR search filter with the provided filter 238 * components. 239 * @throws LDAPException 240 * an LDAPException is thrown if the creation of a filter 241 * component fails. 242 * @throws IOException if a value is an anyURI and cannot be fetched. 243 */ 244 private static LDAPFilter createORFilter(FilterSet filterSet) 245 throws LDAPException, IOException 246 { 247 List<JAXBElement<?>> list = filterSet.getFilterGroup(); 248 ArrayList<RawFilter> filters = new ArrayList<>(list.size()); 249 250 for (JAXBElement<?> filter : list) 251 { 252 filters.add(createFilter(filter)); 253 } 254 return LDAPFilter.createORFilter(filters); 255 } 256 257 258 259 /** 260 * Returns a new Present search filter with the provided 261 * information. 262 * 263 * @param ad 264 * the attribute description for this Present filter. 265 * @returna new Present search filter with the provided information. 266 * @throws LDAPException 267 * an LDAPException is thrown if the ASN.1 element 268 * provided by the attribute description cannot be decoded 269 * as a raw search filter. 270 */ 271 private static LDAPFilter createPresentFilter(AttributeDescription ad) 272 throws LDAPException 273 { 274 return LDAPFilter.decode(ad.getName() + "=*"); 275 } 276 277 278 279 /** 280 * Returns a new Substring search filter with the provided 281 * information. 282 * 283 * @param sf 284 * the substring filter for this Substring filter. 285 * @return a new Substring search filter with the provided 286 * information. 287 * @throws LDAPException if the filter could not be decoded. 288 * @throws IOException if a value is an anyURI and cannot be fetched. 289 */ 290 private static LDAPFilter createSubstringFilter(SubstringFilter sf) 291 throws LDAPException, IOException 292 { 293 List<Object> anyo = sf.getAny(); 294 ArrayList<ByteString> subAnyElements = new ArrayList<>(anyo.size()); 295 296 for (Object o : anyo) 297 { 298 subAnyElements.add(ByteStringUtility.convertValue(o)); 299 } 300 if(sf.getInitial() == null && subAnyElements.isEmpty() 301 && sf.getFinal()==null) 302 { 303 LocalizableMessage message = ERR_LDAP_FILTER_DECODE_NULL.get(); 304 throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message); 305 } 306 return LDAPFilter.createSubstringFilter(sf.getName(), 307 sf.getInitial() == null ? null : ByteStringUtility 308 .convertValue(sf.getInitial()), 309 subAnyElements, 310 sf.getFinal() == null ? null : ByteStringUtility 311 .convertValue(sf.getFinal())); 312 } 313 314 315 316 /** 317 * Returns a new LDAPFilter according to the tag name of the 318 * provided element that can be "and", "or", "not", "equalityMatch", 319 * "substrings", "greaterOrEqual", "lessOrEqual", "present", 320 * "approxMatch", "extensibleMatch". 321 * 322 * @param xmlElement 323 * a JAXBElement that contains the name of the filter to 324 * create and the associated argument. 325 * @return a new LDAPFilter according to the tag name of the 326 * provided element. 327 * @throws LDAPException 328 * an LDAPException is thrown if the creation of the 329 * targeted filter fails. 330 * @throws IOException if a value is an anyURI and cannot be fetched. 331 */ 332 private static LDAPFilter createFilter(JAXBElement<?> xmlElement) 333 throws LDAPException, IOException 334 { 335 LDAPFilter result = null; 336 337 String filterName = xmlElement.getName().getLocalPart(); 338 339 if ("and".equals(filterName)) 340 { 341 // <xsd:element name="and" type="FilterSet"/> 342 result = createANDFilter((FilterSet) xmlElement.getValue()); 343 } 344 else if ("or".equals(filterName)) 345 { 346 // <xsd:element name="or" type="FilterSet"/> 347 result = createORFilter((FilterSet) xmlElement.getValue()); 348 } 349 else if ("not".equals(filterName)) 350 { 351 // <xsd:element name="not" type="Filter"/> 352 result = createNOTFilter((Filter) xmlElement.getValue()); 353 } 354 else if ("equalityMatch".equals(filterName)) 355 { 356 // <xsd:element name="equalityMatch" 357 // type="AttributeValueAssertion"/> 358 result = createEqualityFilter((AttributeValueAssertion) xmlElement 359 .getValue()); 360 } 361 else if ("substrings".equals(filterName)) 362 { 363 // <xsd:element name="substrings" type="SubstringFilter"/> 364 result = createSubstringFilter((SubstringFilter) xmlElement.getValue()); 365 } 366 else if ("greaterOrEqual".equals(filterName)) 367 { 368 // <xsd:element name="greaterOrEqual" 369 // type="AttributeValueAssertion"/> 370 result = createGreaterOrEqualFilter((AttributeValueAssertion) xmlElement 371 .getValue()); 372 } 373 else if ("lessOrEqual".equals(filterName)) 374 { 375 // <xsd:element name="lessOrEqual" 376 // type="AttributeValueAssertion"/> 377 result = createLessOrEqualFilter((AttributeValueAssertion) xmlElement 378 .getValue()); 379 } 380 else if ("present".equals(filterName)) 381 { 382 // <xsd:element name="present" type="AttributeDescription"/> 383 result = 384 createPresentFilter((AttributeDescription) xmlElement.getValue()); 385 } 386 else if ("approxMatch".equals(filterName)) 387 { 388 // <xsd:element name="approxMatch" 389 // type="AttributeValueAssertion"/> 390 result = createApproximateFilter((AttributeValueAssertion) xmlElement 391 .getValue()); 392 } 393 else if ("extensibleMatch".equals(filterName)) 394 { 395 // <xsd:element name="extensibleMatch" 396 // type="MatchingRuleAssertion"/> 397 result = createExtensibleFilter((MatchingRuleAssertion) xmlElement 398 .getValue()); 399 } 400 return result; 401 } 402 403 404 405 /** 406 * Returns a new LDAPFilter according to the filter assigned to the 407 * provided filter. 408 * 409 * @param filter 410 * a filter that contains the object filter to create. 411 * @return a new LDAPFilter according to the filter assigned to the 412 * provided filter. 413 * @throws LDAPException 414 * an LDAPException is thrown if the creation of the 415 * targeted filter fails. 416 * @throws IOException if a value is an anyURI and cannot be fetched. 417 */ 418 private static LDAPFilter createFilter(Filter filter) 419 throws LDAPException, IOException 420 { 421 422 LDAPFilter result = null; 423 424 if (filter.getAnd() != null) 425 { 426 result = createANDFilter(filter.getAnd()); 427 } 428 else if (filter.getApproxMatch() != null) 429 { 430 result = createApproximateFilter(filter.getApproxMatch()); 431 } 432 else if (filter.getEqualityMatch() != null) 433 { 434 result = createEqualityFilter(filter.getEqualityMatch()); 435 } 436 else if (filter.getExtensibleMatch() != null) 437 { 438 result = createExtensibleFilter(filter.getExtensibleMatch()); 439 } 440 else if (filter.getGreaterOrEqual() != null) 441 { 442 result = createGreaterOrEqualFilter(filter.getGreaterOrEqual()); 443 } 444 else if (filter.getLessOrEqual() != null) 445 { 446 result = createLessOrEqualFilter(filter.getLessOrEqual()); 447 } 448 else if (filter.getNot() != null) 449 { 450 result = createNOTFilter(filter.getNot()); 451 } 452 else if (filter.getOr() != null) 453 { 454 result = createORFilter(filter.getOr()); 455 } 456 else if (filter.getPresent() != null) 457 { 458 result = createPresentFilter(filter.getPresent()); 459 } 460 else if (filter.getSubstrings() != null) 461 { 462 result = createSubstringFilter(filter.getSubstrings()); 463 } 464 return result; 465 } 466 467 468 469 /** 470 * Perform the LDAP SEARCH operation and send the result back to the 471 * client. 472 * 473 * @param objFactory 474 * The object factory for this operation. 475 * @param searchRequest 476 * The search request for this operation. 477 * @param controls 478 * Any required controls (e.g. for proxy authz). 479 * @return The result of the search operation. 480 * @throws IOException 481 * If an I/O problem occurs. 482 * @throws LDAPException 483 * If an error occurs while interacting with an LDAP 484 * element. 485 */ 486 public SearchResponse doSearch(ObjectFactory objFactory, 487 SearchRequest searchRequest, 488 List<org.opends.server.types.Control> controls) 489 throws IOException, LDAPException 490 { 491 SearchResponse searchResponse = objFactory.createSearchResponse(); 492 searchResponse.setRequestID(searchRequest.getRequestID()); 493 494 LDAPFilter filter = createFilter(searchRequest.getFilter()); 495 496 DereferenceAliasesPolicy derefPolicy = DereferenceAliasesPolicy.NEVER; 497 String derefStr = searchRequest.getDerefAliases().toLowerCase(); 498 if (derefStr.equals("derefinsearching")) 499 { 500 derefPolicy = DereferenceAliasesPolicy.IN_SEARCHING; 501 } 502 else if (derefStr.equals("dereffindingbaseobj")) 503 { 504 derefPolicy = DereferenceAliasesPolicy.FINDING_BASE; 505 } 506 else if (derefStr.equals("derefalways")) 507 { 508 derefPolicy = DereferenceAliasesPolicy.ALWAYS; 509 } 510 511 SearchScope scope = SearchScope.WHOLE_SUBTREE; 512 String scopeStr = searchRequest.getScope().toLowerCase(); 513 if (scopeStr.equals("singlelevel") || scopeStr.equals("one")) 514 { 515 scope = SearchScope.SINGLE_LEVEL; 516 } 517 else if (scopeStr.equals("baseobject") || scopeStr.equals("base")) 518 { 519 scope = SearchScope.BASE_OBJECT; 520 } 521 522 LinkedHashSet<String> attributes = new LinkedHashSet<>(); 523 // Get the list of attributes. 524 AttributeDescriptions attrDescriptions = searchRequest.getAttributes(); 525 if (attrDescriptions != null) 526 { 527 List<AttributeDescription> attrDesc = attrDescriptions.getAttribute(); 528 for (AttributeDescription desc : attrDesc) 529 { 530 attributes.add(desc.getName()); 531 } 532 } 533 534 SearchRequestProtocolOp protocolOp = new SearchRequestProtocolOp(ByteString 535 .valueOf(searchRequest.getDn()), scope, derefPolicy, 536 (int) searchRequest.getSizeLimit(), (int) searchRequest.getTimeLimit(), 537 searchRequest.isTypesOnly(), filter, attributes); 538 try 539 { 540 LDAPMessage msg = 541 new LDAPMessage(DSMLServlet.nextMessageID(), protocolOp, controls); 542 connection.getLDAPWriter().writeMessage(msg); 543 544 byte opType; 545 do 546 { 547 int resultCode = 0; 548 LocalizableMessage errorMessage = null; 549 LDAPMessage responseMessage = connection.getLDAPReader().readMessage(); 550 if(responseMessage == null) 551 { 552 //The server disconnected silently. At this point we don't know if it 553 // is a protocol error or anything else. Since we didn't hear from 554 // the server , we have a reason to believe that the server doesn't 555 // want to handle this request. Let us return unavailable error 556 // code to the client to cover possible cases. 557 LocalizableMessage message = ERR_UNEXPECTED_CONNECTION_CLOSURE.get(); 558 LDAPResult result = objFactory.createLDAPResult(); 559 ResultCode code = ResultCodeFactory.create(objFactory, 560 LDAPResultCode.UNAVAILABLE); 561 result.setResultCode(code); 562 result.setErrorMessage(message.toString()); 563 searchResponse.setSearchResultDone(result); 564 return searchResponse; 565 } 566 opType = responseMessage.getProtocolOpType(); 567 switch (opType) 568 { 569 case LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY: 570 SearchResultEntryProtocolOp searchEntryOp = responseMessage 571 .getSearchResultEntryProtocolOp(); 572 573 SearchResultEntry entry = objFactory.createSearchResultEntry(); 574 java.util.List<DsmlAttr> attrList = entry.getAttr(); 575 576 LinkedList<LDAPAttribute> attrs = searchEntryOp.getAttributes(); 577 578 for (LDAPAttribute attr : attrs) 579 { 580 String nm = attr.getAttributeType(); 581 DsmlAttr dsmlAttr = objFactory.createDsmlAttr(); 582 583 dsmlAttr.setName(nm); 584 List<Object> dsmlAttrVal = dsmlAttr.getValue(); 585 List<ByteString> vals = attr.getValues(); 586 for (ByteString val : vals) 587 { 588 dsmlAttrVal.add(ByteStringUtility.convertByteString(val)); 589 } 590 attrList.add(dsmlAttr); 591 } 592 593 entry.setDn(searchEntryOp.getDN().toString()); 594 searchResponse.getSearchResultEntry().add(entry); 595 break; 596 597 case LDAPConstants.OP_TYPE_SEARCH_RESULT_REFERENCE: 598 responseMessage.getSearchResultReferenceProtocolOp(); 599 break; 600 601 case LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE: 602 SearchResultDoneProtocolOp searchOp = responseMessage 603 .getSearchResultDoneProtocolOp(); 604 resultCode = searchOp.getResultCode(); 605 errorMessage = searchOp.getErrorMessage(); 606 LDAPResult result = objFactory.createLDAPResult(); 607 ResultCode code = ResultCodeFactory.create(objFactory, resultCode); 608 result.setResultCode(code); 609 result.setErrorMessage(errorMessage != null ? errorMessage.toString() 610 : null); 611 if (searchOp.getMatchedDN() != null) 612 { 613 result.setMatchedDN(searchOp.getMatchedDN().toString()); 614 } 615 searchResponse.setSearchResultDone(result); 616 break; 617 default: 618 throw new RuntimeException("Invalid protocol operation:" + opType); 619 } 620 } 621 while (opType != LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE); 622 623 } 624 catch (DecodeException ae) 625 { 626 ae.printStackTrace(); 627 throw new IOException(ae.getMessage()); 628 } 629 630 return searchResponse; 631 } 632}