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