1 /*
   2  * Copyright 2001-2002 Sun Microsystems, Inc.  All rights reserved.
   3  * Use is subject to license terms.
   4  *
   5  * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
   6  */
   7 
   8 
   9 /*
  10  * The contents of this file are subject to the Netscape Public
  11  * License Version 1.1 (the "License"); you may not use this file
  12  * except in compliance with the License. You may obtain a copy of
  13  * the License at http://www.mozilla.org/NPL/
  14  *
  15  * Software distributed under the License is distributed on an "AS
  16  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  17  * implied. See the License for the specific language governing
  18  * rights and limitations under the License.
  19  *
  20  * The Original Code is Mozilla Communicator client code, released
  21  * March 31, 1998.
  22  *
  23  * The Initial Developer of the Original Code is Netscape
  24  * Communications Corporation. Portions created by Netscape are
  25  * Copyright (C) 1998-1999 Netscape Communications Corporation. All
  26  * Rights Reserved.
  27  *
  28  * Contributor(s):
  29  */
  30 /*
  31  *  Copyright (c) 1990 Regents of the University of Michigan.
  32  *  All rights reserved.
  33  */
  34 /*
  35  *  search.c
  36  */
  37 
  38 #if 0
  39 #ifndef lint 
  40 static char copyright[] = "@(#) Copyright (c) 1990 Regents of the University of Michigan.\nAll rights reserved.\n";
  41 #endif
  42 #endif
  43 
  44 #include "ldap-int.h"
  45 
  46 static int nsldapi_timeval2ldaplimit( struct timeval *timeoutp,
  47         int defaultvalue );
  48 static int nsldapi_search( LDAP *ld, const char *base, int scope,
  49         const char *filter, char **attrs, int attrsonly,
  50         LDAPControl **serverctrls, LDAPControl **clientctrls,
  51         int timelimit, int sizelimit, int *msgidp );
  52 static char *find_right_paren( char *s );
  53 static char *put_complex_filter( BerElement *ber, char *str,
  54         ber_tag_t tag, int not );
  55 static int unescape_filterval( char *str );
  56 static int hexchar2int( char c );
  57 static int is_valid_attr( char *a );
  58 static int put_simple_filter( BerElement *ber, char *str );
  59 static int put_substring_filter( BerElement *ber, char *type,
  60         char *str );
  61 static int put_filter_list( BerElement *ber, char *str );
  62 static int nsldapi_search_s( LDAP *ld, const char *base, int scope, 
  63         const char *filter, char **attrs, int attrsonly,
  64         LDAPControl **serverctrls, LDAPControl **clientctrls,
  65         struct timeval *localtimeoutp, int timelimit, int sizelimit,
  66         LDAPMessage **res );
  67 
  68 /*
  69  * ldap_search - initiate an ldap search operation.  Parameters:
  70  *
  71  *      ld              LDAP descriptor
  72  *      base            DN of the base object
  73  *      scope           the search scope - one of LDAP_SCOPE_BASE,
  74  *                          LDAP_SCOPE_ONELEVEL, LDAP_SCOPE_SUBTREE
  75  *      filter          a string containing the search filter
  76  *                      (e.g., "(|(cn=bob)(sn=bob))")
  77  *      attrs           list of attribute types to return for matches
  78  *      attrsonly       1 => attributes only 0 => attributes and values
  79  *
  80  * Example:
  81  *      char    *attrs[] = { "mail", "title", 0 };
  82  *      msgid = ldap_search( ld, "c=us@o=UM", LDAP_SCOPE_SUBTREE, "cn~=bob",
  83  *          attrs, attrsonly );
  84  */
  85 int
  86 LDAP_CALL
  87 ldap_search(
  88     LDAP        *ld,
  89     const char  *base,
  90     int         scope,
  91     const char  *filter,
  92     char        **attrs,
  93     int         attrsonly
  94 )
  95 {
  96         int             msgid;
  97 
  98         LDAPDebug( LDAP_DEBUG_TRACE, "ldap_search\n", 0, 0, 0 );
  99 
 100         if ( ldap_search_ext( ld, base, scope, filter, attrs, attrsonly, NULL,
 101             NULL, NULL, -1, &msgid ) == LDAP_SUCCESS ) {
 102                 return( msgid );
 103         } else {
 104                 return( -1 );   /* error is in ld handle */
 105         }
 106 }
 107 
 108 
 109 /*
 110  * LDAPv3 extended search.
 111  * Returns an LDAP error code.
 112  */
 113 int
 114 LDAP_CALL
 115 ldap_search_ext(
 116     LDAP                *ld,
 117     const char          *base,
 118     int                 scope,
 119     const char          *filter,
 120     char                **attrs,
 121     int                 attrsonly,
 122     LDAPControl         **serverctrls,
 123     LDAPControl         **clientctrls,
 124     struct timeval      *timeoutp,      /* NULL means use ld->ld_timelimit */
 125     int                 sizelimit,
 126     int                 *msgidp
 127 )
 128 {
 129         /*
 130          * It is an error to pass in a zero'd timeval.
 131          */
 132         if ( timeoutp != NULL && timeoutp->tv_sec == 0 &&
 133             timeoutp->tv_usec == 0 ) {
 134                 if ( ld != NULL ) {
 135                         LDAP_SET_LDERRNO( ld, LDAP_PARAM_ERROR, NULL, NULL );
 136                 }
 137                 return( LDAP_PARAM_ERROR );
 138         }
 139 
 140         return( nsldapi_search( ld, base, scope, filter, attrs, attrsonly,
 141             serverctrls, clientctrls,
 142             nsldapi_timeval2ldaplimit( timeoutp, -1 ), sizelimit, msgidp ));
 143 }
 144 
 145 
 146 /*
 147  * Like ldap_search_ext() except an integer timelimit is passed instead of
 148  * using the overloaded struct timeval *timeoutp.
 149  */
 150 static int
 151 nsldapi_search(
 152     LDAP                *ld,
 153     const char          *base,
 154     int                 scope,
 155     const char          *filter,
 156     char                **attrs,
 157     int                 attrsonly,
 158     LDAPControl         **serverctrls,
 159     LDAPControl         **clientctrls,
 160     int                 timelimit,      /* -1 means use ld->ld_timelimit */
 161     int                 sizelimit,      /* -1 means use ld->ld_sizelimit */
 162     int                 *msgidp
 163 )
 164 {
 165         BerElement      *ber;
 166         int             rc, rc_key;
 167         unsigned long   key;    /* XXXmcs: memcache */
 168 
 169         LDAPDebug( LDAP_DEBUG_TRACE, "ldap_search_ext\n", 0, 0, 0 );
 170 
 171         if ( !NSLDAPI_VALID_LDAP_POINTER( ld )) {
 172                 return( LDAP_PARAM_ERROR );
 173         }
 174 
 175         if ( base == NULL ) {
 176             base = "";
 177         }
 178 
 179         if ( filter == NULL ) {
 180             filter = "(objectclass=*)";
 181         }
 182 
 183         if ( msgidp == NULL || ( scope != LDAP_SCOPE_BASE
 184             && scope != LDAP_SCOPE_ONELEVEL && scope != LDAP_SCOPE_SUBTREE )
 185                 || ( sizelimit < -1 )) {
 186                 LDAP_SET_LDERRNO( ld, LDAP_PARAM_ERROR, NULL, NULL );
 187                 return( LDAP_PARAM_ERROR );
 188         }
 189         LDAP_MUTEX_LOCK( ld, LDAP_MSGID_LOCK );
 190         *msgidp = ++ld->ld_msgid;
 191         LDAP_MUTEX_UNLOCK( ld, LDAP_MSGID_LOCK );
 192 
 193         /*
 194          * XXXmcs: should use cache function pointers to hook in memcache
 195          */
 196         if ( ld->ld_memcache == NULL ) {
 197                 rc_key = LDAP_NOT_SUPPORTED;
 198         } else if (( rc_key = ldap_memcache_createkey( ld, base, scope, filter,
 199             attrs, attrsonly, serverctrls, clientctrls, &key)) == LDAP_SUCCESS
 200             && ldap_memcache_result( ld, *msgidp, key ) == LDAP_SUCCESS ) {
 201                 return LDAP_SUCCESS;
 202         }
 203 
 204         /* check the cache */
 205         if ( ld->ld_cache_on && ld->ld_cache_search != NULL ) {
 206                 LDAP_MUTEX_LOCK( ld, LDAP_CACHE_LOCK );
 207                 if ( (rc = (ld->ld_cache_search)( ld, *msgidp, LDAP_REQ_SEARCH,
 208                     base, scope, filter, attrs, attrsonly )) != 0 ) {
 209                         *msgidp = rc;
 210                         LDAP_MUTEX_UNLOCK( ld, LDAP_CACHE_LOCK );
 211                         return( LDAP_SUCCESS );
 212                 }
 213                 LDAP_MUTEX_UNLOCK( ld, LDAP_CACHE_LOCK );
 214         }
 215 
 216         /* caching off or did not find it in the cache - check the net */
 217         if (( rc = nsldapi_build_search_req( ld, base, scope, filter, attrs,
 218             attrsonly, serverctrls, clientctrls, timelimit, sizelimit,
 219             *msgidp, &ber )) != LDAP_SUCCESS ) {
 220                 return( rc );
 221         }
 222 
 223         /* send the message */
 224         rc = nsldapi_send_initial_request( ld, *msgidp, LDAP_REQ_SEARCH,
 225                 (char *) base, ber );
 226 
 227         /*
 228          * XXXmcs: should use cache function pointers to hook in memcache
 229          */
 230         if ( (rc_key == LDAP_SUCCESS) && (rc >= 0) ) {
 231                 ldap_memcache_new( ld, rc, key, base );
 232         }
 233 
 234         *msgidp = rc;
 235         return( rc < 0 ? LDAP_GET_LDERRNO( ld, NULL, NULL ) : LDAP_SUCCESS );
 236 }
 237 
 238 
 239 /*
 240  * Convert a non-NULL timeoutp to a value in seconds that is appropriate to
 241  * send in an LDAP search request.  If timeoutp is NULL, return defaultvalue.
 242  */
 243 static int
 244 nsldapi_timeval2ldaplimit( struct timeval *timeoutp, int defaultvalue )
 245 {
 246         int             timelimit;
 247 
 248         if ( NULL == timeoutp ) {
 249                 timelimit = defaultvalue;
 250         } else if ( timeoutp->tv_sec > 0 ) {
 251                 timelimit = timeoutp->tv_sec;
 252         } else if ( timeoutp->tv_usec > 0 ) {
 253                 timelimit = 1;  /* minimum we can express in LDAP */
 254         } else {
 255                 /*
 256                  * both tv_sec and tv_usec are less than one (zero?) so
 257                  * to maintain compatiblity with our "zero means no limit"
 258                  * convention we pass no limit to the server.
 259                  */
 260                 timelimit = 0;  /* no limit */
 261         }
 262 
 263         return( timelimit );
 264 }
 265 
 266 
 267 /* returns an LDAP error code and also sets it in ld */
 268 int
 269 nsldapi_build_search_req(
 270     LDAP                *ld, 
 271     const char          *base, 
 272     int                 scope, 
 273     const char          *filter,
 274     char                **attrs, 
 275     int                 attrsonly,
 276     LDAPControl         **serverctrls,
 277     LDAPControl         **clientctrls,  /* not used for anything yet */
 278     int                 timelimit,      /* if -1, ld->ld_timelimit is used */
 279     int                 sizelimit,      /* if -1, ld->ld_sizelimit is used */
 280     int                 msgid,
 281     BerElement          **berp
 282 )
 283 {
 284         BerElement      *ber;
 285         int             err;
 286         char            *fdup;
 287 
 288         /*
 289          * Create the search request.  It looks like this:
 290          *      SearchRequest := [APPLICATION 3] SEQUENCE {
 291          *              baseObject      DistinguishedName,
 292          *              scope           ENUMERATED {
 293          *                      baseObject      (0),
 294          *                      singleLevel     (1),
 295          *                      wholeSubtree    (2)
 296          *              },
 297          *              derefAliases    ENUMERATED {
 298          *                      neverDerefaliases       (0),
 299          *                      derefInSearching        (1),
 300          *                      derefFindingBaseObj     (2),
 301          *                      alwaysDerefAliases      (3)
 302          *              },
 303          *              sizelimit       INTEGER (0 .. 65535),
 304          *              timelimit       INTEGER (0 .. 65535),
 305          *              attrsOnly       BOOLEAN,
 306          *              filter          Filter,
 307          *              attributes      SEQUENCE OF AttributeType
 308          *      }
 309          * wrapped in an ldap message.
 310          */
 311 
 312         /* create a message to send */
 313         if (( err = nsldapi_alloc_ber_with_options( ld, &ber ))
 314             != LDAP_SUCCESS ) {
 315                 return( err );
 316         }
 317 
 318         if ( base == NULL ) {
 319             base = "";
 320         }
 321 
 322         if ( sizelimit == -1 ) {
 323             sizelimit = ld->ld_sizelimit;
 324         }
 325 
 326         if ( timelimit == -1 ) {
 327             timelimit = ld->ld_timelimit;
 328         }
 329 
 330 #ifdef CLDAP
 331         if ( ld->ld_sbp->sb_naddr > 0 ) {
 332             err = ber_printf( ber, "{ist{seeiib", msgid,
 333                 ld->ld_cldapdn, LDAP_REQ_SEARCH, base, scope, ld->ld_deref,
 334                 sizelimit, timelimit, attrsonly );
 335         } else {
 336 #endif /* CLDAP */
 337                 err = ber_printf( ber, "{it{seeiib", msgid,
 338                     LDAP_REQ_SEARCH, base, scope, ld->ld_deref,
 339                     sizelimit, timelimit, attrsonly );
 340 #ifdef CLDAP
 341         }
 342 #endif /* CLDAP */
 343 
 344         if ( err == -1 ) {
 345                 LDAP_SET_LDERRNO( ld, LDAP_ENCODING_ERROR, NULL, NULL );
 346                 ber_free( ber, 1 );
 347                 return( LDAP_ENCODING_ERROR );
 348         }
 349 
 350         fdup = nsldapi_strdup( filter );
 351         if (fdup == NULL) {
 352                 LDAP_SET_LDERRNO( ld, LDAP_NO_MEMORY, NULL, NULL );
 353                 ber_free( ber, 1 );
 354                 return( LDAP_NO_MEMORY );
 355         }
 356         err = ldap_put_filter( ber, fdup );
 357         NSLDAPI_FREE( fdup );
 358 
 359         if ( err == -1 ) {
 360                 LDAP_SET_LDERRNO( ld, LDAP_FILTER_ERROR, NULL, NULL );
 361                 ber_free( ber, 1 );
 362                 return( LDAP_FILTER_ERROR );
 363         }
 364 
 365         if ( ber_printf( ber, "{v}}", attrs ) == -1 ) {
 366                 LDAP_SET_LDERRNO( ld, LDAP_ENCODING_ERROR, NULL, NULL );
 367                 ber_free( ber, 1 );
 368                 return( LDAP_ENCODING_ERROR );
 369         }
 370 
 371         if ( (err = nsldapi_put_controls( ld, serverctrls, 1, ber ))
 372             != LDAP_SUCCESS ) {
 373                 ber_free( ber, 1 );
 374                 return( err );
 375         }
 376 
 377         *berp = ber;
 378         return( LDAP_SUCCESS );
 379 }
 380 
 381 static char *
 382 find_right_paren( char *s )
 383 {
 384         int     balance, escape;
 385 
 386         balance = 1;
 387         escape = 0;
 388         while ( *s && balance ) {
 389                 if ( escape == 0 ) {
 390                         if ( *s == '(' )
 391                                 balance++;
 392                         else if ( *s == ')' )
 393                                 balance--;
 394                 }
 395                 if ( *s == '\\' && ! escape )
 396                         escape = 1;
 397                 else
 398                         escape = 0;
 399                 if ( balance )
 400                         s++;
 401         }
 402 
 403         return( *s ? s : NULL );
 404 }
 405 
 406 static char *
 407 put_complex_filter(
 408     BerElement          *ber, 
 409     char                *str, 
 410     ber_tag_t           tag, 
 411     int                 not 
 412 )
 413 {
 414         char    *next;
 415 
 416         /*
 417          * We have (x(filter)...) with str sitting on
 418          * the x.  We have to find the paren matching
 419          * the one before the x and put the intervening
 420          * filters by calling put_filter_list().
 421          */
 422 
 423         /* put explicit tag */
 424         if ( ber_printf( ber, "t{", tag ) == -1 )
 425                 return( NULL );
 426 
 427         str++;
 428         if ( (next = find_right_paren( str )) == NULL )
 429                 return( NULL );
 430 
 431         *next = '\0';
 432         if ( put_filter_list( ber, str ) == -1 )
 433                 return( NULL );
 434         *next++ = ')';
 435 
 436         /* flush explicit tagged thang */
 437         if ( ber_printf( ber, "}" ) == -1 )
 438                 return( NULL );
 439 
 440         return( next );
 441 }
 442 
 443 int
 444 ldap_put_filter( BerElement *ber, char *str )
 445 {
 446         char    *next;
 447         int     parens, balance, escape;
 448 
 449         /*
 450          * A Filter looks like this:
 451          *      Filter ::= CHOICE {
 452          *              and             [0]     SET OF Filter,
 453          *              or              [1]     SET OF Filter,
 454          *              not             [2]     Filter,
 455          *              equalityMatch   [3]     AttributeValueAssertion,
 456          *              substrings      [4]     SubstringFilter,
 457          *              greaterOrEqual  [5]     AttributeValueAssertion,
 458          *              lessOrEqual     [6]     AttributeValueAssertion,
 459          *              present         [7]     AttributeType,,
 460          *              approxMatch     [8]     AttributeValueAssertion
 461          *      }
 462          *
 463          *      SubstringFilter ::= SEQUENCE {
 464          *              type               AttributeType,
 465          *              SEQUENCE OF CHOICE {
 466          *                      initial          [0] IA5String,
 467          *                      any              [1] IA5String,
 468          *                      final            [2] IA5String
 469          *              }
 470          *      }
 471          * Note: tags in a choice are always explicit
 472          */
 473 
 474         LDAPDebug( LDAP_DEBUG_TRACE, "put_filter \"%s\"\n", str, 0, 0 );
 475 
 476         parens = 0;
 477         while ( *str ) {
 478                 switch ( *str ) {
 479                 case '(':
 480                         str++;
 481                         parens++;
 482                         switch ( *str ) {
 483                         case '&':
 484                                 LDAPDebug( LDAP_DEBUG_TRACE, "put_filter: AND\n",
 485                                     0, 0, 0 );
 486 
 487                                 if ( (str = put_complex_filter( ber, str,
 488                                     LDAP_FILTER_AND, 0 )) == NULL )
 489                                         return( -1 );
 490 
 491                                 parens--;
 492                                 break;
 493 
 494                         case '|':
 495                                 LDAPDebug( LDAP_DEBUG_TRACE, "put_filter: OR\n",
 496                                     0, 0, 0 );
 497 
 498                                 if ( (str = put_complex_filter( ber, str,
 499                                     LDAP_FILTER_OR, 0 )) == NULL )
 500                                         return( -1 );
 501 
 502                                 parens--;
 503                                 break;
 504 
 505                         case '!':
 506                                 LDAPDebug( LDAP_DEBUG_TRACE, "put_filter: NOT\n",
 507                                     0, 0, 0 );
 508 
 509                                 if ( (str = put_complex_filter( ber, str,
 510                                     LDAP_FILTER_NOT, 1 )) == NULL )
 511                                         return( -1 );
 512 
 513                                 parens--;
 514                                 break;
 515 
 516                         default:
 517                                 LDAPDebug( LDAP_DEBUG_TRACE,
 518                                     "put_filter: simple\n", 0, 0, 0 );
 519 
 520                                 balance = 1;
 521                                 escape = 0;
 522                                 next = str;
 523                                 while ( *next && balance ) {
 524                                         if ( escape == 0 ) {
 525                                                 if ( *next == '(' )
 526                                                         balance++;
 527                                                 else if ( *next == ')' )
 528                                                         balance--;
 529                                         }
 530                                         if ( *next == '\\' && ! escape )
 531                                                 escape = 1;
 532                                         else
 533                                                 escape = 0;
 534                                         if ( balance )
 535                                                 next++;
 536                                 }
 537                                 if ( balance != 0 )
 538                                         return( -1 );
 539 
 540                                 *next = '\0';
 541                                 if ( put_simple_filter( ber, str ) == -1 ) {
 542                                         return( -1 );
 543                                 }
 544                                 *next++ = ')';
 545                                 str = next;
 546                                 parens--;
 547                                 break;
 548                         }
 549                         break;
 550 
 551                 case ')':
 552                         LDAPDebug( LDAP_DEBUG_TRACE, "put_filter: end\n", 0, 0,
 553                             0 );
 554                         if ( ber_printf( ber, "]" ) == -1 )
 555                                 return( -1 );
 556                         str++;
 557                         parens--;
 558                         break;
 559 
 560                 case ' ':
 561                         str++;
 562                         break;
 563 
 564                 default:        /* assume it's a simple type=value filter */
 565                         LDAPDebug( LDAP_DEBUG_TRACE, "put_filter: default\n", 0, 0,
 566                             0 );
 567                         next = strchr( str, '\0' );
 568                         if ( put_simple_filter( ber, str ) == -1 ) {
 569                                 return( -1 );
 570                         }
 571                         str = next;
 572                         break;
 573                 }
 574         }
 575 
 576         return( parens ? -1 : 0 );
 577 }
 578 
 579 
 580 /*
 581  * Put a list of filters like this "(filter1)(filter2)..."
 582  */
 583 
 584 static int
 585 put_filter_list( BerElement *ber, char *str )
 586 {
 587         char    *next;
 588         char    save;
 589 
 590         LDAPDebug( LDAP_DEBUG_TRACE, "put_filter_list \"%s\"\n", str, 0, 0 );
 591 
 592         while ( *str ) {
 593                 while ( *str && isspace( *str ) )
 594                         str++;
 595                 if ( *str == '\0' )
 596                         break;
 597 
 598                 if ( (next = find_right_paren( str + 1 )) == NULL )
 599                         return( -1 );
 600                 save = *++next;
 601 
 602                 /* now we have "(filter)" with str pointing to it */
 603                 *next = '\0';
 604                 if ( ldap_put_filter( ber, str ) == -1 )
 605                         return( -1 );
 606                 *next = save;
 607 
 608                 str = next;
 609         }
 610 
 611         return( 0 );
 612 }
 613 
 614 
 615 /*
 616  * is_valid_attr - returns 1 if a is a syntactically valid left-hand side
 617  * of a filter expression, 0 otherwise.  A valid string may contain only
 618  * letters, numbers, hyphens, semi-colons, colons and periods. examples:
 619  *      cn
 620  *      cn;lang-fr
 621  *      1.2.3.4;binary;dynamic
 622  *      mail;dynamic
 623  *      cn:dn:1.2.3.4
 624  *
 625  * For compatibility with older servers, we also allow underscores in
 626  * attribute types, even through they are not allowed by the LDAPv3 RFCs.
 627  */
 628 static int
 629 is_valid_attr( char *a )
 630 {
 631         for ( ; *a; a++ ) {
 632             if ( !isascii( *a ) ) {
 633                 return( 0 );
 634             } else if ( !isalnum( *a ) ) {
 635                 switch ( *a ) {
 636                   case '-':
 637                   case '.':
 638                   case ';':
 639                   case ':':
 640                   case '_':
 641                     break; /* valid */
 642                   default:
 643                     return( 0 );
 644                 }
 645             }
 646         }
 647 
 648         return( 1 );
 649 }
 650 
 651 static char *
 652 find_star( char *s )
 653 {
 654     for ( ; *s; ++s ) {
 655         switch ( *s ) {
 656           case '*': return s;
 657           case '\\':
 658             ++s;
 659             if ( hexchar2int(s[0]) >= 0 && hexchar2int(s[1]) >= 0 ) ++s;
 660           default: break;
 661         }
 662     }
 663     return NULL;
 664 }
 665 
 666 static int
 667 put_simple_filter( BerElement *ber, char *str )
 668 {
 669         char            *s, *s2, *s3, filterop;
 670         char            *value;
 671         ber_uint_t      ftype;
 672         int             rc, len;
 673         char            *oid;   /* for v3 extended filter */
 674         int             dnattr; /* for v3 extended filter */
 675 
 676         LDAPDebug( LDAP_DEBUG_TRACE, "put_simple_filter \"%s\"\n", str, 0, 0 );
 677 
 678         rc = -1;        /* pessimistic */
 679 
 680         if (( str = nsldapi_strdup( str )) == NULL ) {
 681                 return( rc );
 682         }
 683 
 684         if ( (s = strchr( str, '=' )) == NULL ) {
 685                 goto free_and_return;
 686         }
 687         value = s + 1;
 688         *s-- = '\0';
 689         filterop = *s;
 690         if ( filterop == '<' || filterop == '>' || filterop == '~' ||
 691             filterop == ':' ) {
 692                 *s = '\0';
 693         }
 694 
 695         if ( ! is_valid_attr( str ) ) {
 696                 goto free_and_return;
 697         }
 698 
 699         switch ( filterop ) {
 700         case '<':
 701                 ftype = LDAP_FILTER_LE;
 702                 break;
 703         case '>':
 704                 ftype = LDAP_FILTER_GE;
 705                 break;
 706         case '~':
 707                 ftype = LDAP_FILTER_APPROX;
 708                 break;
 709         case ':':       /* extended filter - v3 only */
 710                 /*
 711                  * extended filter looks like this:
 712                  *
 713                  *      [type][':dn'][':'oid]':='value
 714                  *
 715                  * where one of type or :oid is required.
 716                  *
 717                  */
 718                 ftype = LDAP_FILTER_EXTENDED;
 719                 s2 = s3 = NULL;
 720                 if ( (s2 = strrchr( str, ':' )) == NULL ) {
 721                         goto free_and_return;
 722                 }
 723                 if ( strcasecmp( s2, ":dn" ) == 0 ) {
 724                         oid = NULL;
 725                         dnattr = 1;
 726                         *s2 = '\0';
 727                 } else {
 728                         oid = s2 + 1;
 729                         dnattr = 0;
 730                         *s2 = '\0';
 731                         if ( (s3 = strrchr( str, ':' )) != NULL ) {
 732                                 if ( strcasecmp( s3, ":dn" ) == 0 ) {
 733                                         dnattr = 1;
 734                                 } else {
 735                                         goto free_and_return;
 736                                 }
 737                                 *s3 = '\0';
 738                         }
 739                 }
 740                 if ( (rc = ber_printf( ber, "t{", ftype )) == -1 ) {
 741                         goto free_and_return;
 742                 }
 743                 if ( oid != NULL ) {
 744                         if ( (rc = ber_printf( ber, "ts", LDAP_TAG_MRA_OID,
 745                             oid )) == -1 ) {
 746                                 goto free_and_return;
 747                         }
 748                 }
 749                 if ( *str != '\0' ) {
 750                         if ( (rc = ber_printf( ber, "ts",
 751                             LDAP_TAG_MRA_TYPE, str )) == -1 ) {
 752                                 goto free_and_return;
 753                         }
 754                 }
 755                 if (( len = unescape_filterval( value )) < 0 ||
 756                     ( rc = ber_printf( ber, "totb}", LDAP_TAG_MRA_VALUE,
 757                     value, len, LDAP_TAG_MRA_DNATTRS, dnattr )) == -1 ) {
 758                         goto free_and_return;
 759                 }
 760                 rc = 0;
 761                 goto free_and_return;
 762                 /* break; */
 763         default:
 764                 if ( find_star( value ) == NULL ) {
 765                         ftype = LDAP_FILTER_EQUALITY;
 766                 } else if ( strcmp( value, "*" ) == 0 ) {
 767                         ftype = LDAP_FILTER_PRESENT;
 768                 } else {
 769                         rc = put_substring_filter( ber, str, value );
 770                         goto free_and_return;
 771                 }
 772                 break;
 773         }
 774 
 775         if ( ftype == LDAP_FILTER_PRESENT ) {
 776                 rc = ber_printf( ber, "ts", ftype, str );
 777         } else if (( len = unescape_filterval( value )) >= 0 ) {
 778                 rc = ber_printf( ber, "t{so}", ftype, str, value, len );
 779         }
 780         if ( rc != -1 ) {
 781                 rc = 0;
 782         }
 783 
 784 free_and_return:
 785         NSLDAPI_FREE( str );
 786         return( rc );
 787 }
 788 
 789 
 790 /*
 791  * Undo in place both LDAPv2 (RFC-1960) and LDAPv3 (hexadecimal) escape
 792  * sequences within the null-terminated string 'val'.  The resulting value
 793  * may contain null characters.
 794  *
 795  * If 'val' contains invalid escape sequences we return -1.
 796  * Otherwise the length of the unescaped value is returned.
 797  */
 798 static int
 799 unescape_filterval( char *val )
 800 {
 801         int     escape, firstdigit, ival; 
 802         char    *s, *d;
 803 
 804         escape = 0;
 805         for ( s = d = val; *s; s++ ) {
 806                 if ( escape ) {
 807                         /*
 808                          * need to leave escaped comma as-is, i.e.
 809                          * val="CN=Last\, First,OU=..."
 810                          */
 811                         if (*s == ',') {
 812                                 *d++ = '\\';
 813                                 *d++ = *s;
 814                                 escape = 0;
 815                                 continue;
 816                         }
 817                         /*
 818                          * first try LDAPv3 escape (hexadecimal) sequence
 819                          */
 820                         if (( ival = hexchar2int( *s )) < 0 ) {
 821                                 if ( firstdigit ) {
 822                                         /*
 823                                          * LDAPv2 (RFC1960) escape sequence
 824                                          */
 825                                         *d++ = *s;
 826                                         escape = 0;
 827                                 } else {
 828                                         return(-1);
 829                                 }
 830                         }
 831                         if ( firstdigit ) {
 832                             *d = ( ival<<4 );
 833                             firstdigit = 0;
 834                         } else {
 835                             *d++ |= ival;
 836                             escape = 0;
 837                         }
 838 
 839                 } else if ( *s != '\\' ) {
 840                         *d++ = *s;
 841                         escape = 0;
 842 
 843                 } else {
 844                         escape = 1;
 845                         firstdigit = 1;
 846                 }
 847         }
 848 
 849         return( d - val );
 850 }
 851 
 852 
 853 /*
 854  * convert character 'c' that represents a hexadecimal digit to an integer.
 855  * if 'c' is not a hexidecimal digit [0-9A-Fa-f], -1 is returned.
 856  * otherwise the converted value is returned.
 857  */
 858 static int
 859 hexchar2int( char c )
 860 {
 861     if ( c >= '0' && c <= '9' ) {
 862         return( c - '0' );
 863     }
 864     if ( c >= 'A' && c <= 'F' ) {
 865         return( c - 'A' + 10 );
 866     }
 867     if ( c >= 'a' && c <= 'f' ) {
 868         return( c - 'a' + 10 );
 869     }
 870     return( -1 );
 871 }
 872 
 873 static int
 874 put_substring_filter( BerElement *ber, char *type, char *val )
 875 {
 876         char            *nextstar, gotstar = 0;
 877         ber_uint_t      ftype;
 878         int             len;
 879 
 880         LDAPDebug( LDAP_DEBUG_TRACE, "put_substring_filter \"%s=%s\"\n", type,
 881             val, 0 );
 882 
 883         if ( ber_printf( ber, "t{s{", LDAP_FILTER_SUBSTRINGS, type ) == -1 ) {
 884                 return( -1 );
 885         }
 886 
 887         for ( ; val != NULL; val = nextstar ) {
 888                 if ( (nextstar = find_star( val )) != NULL ) {
 889                         *nextstar++ = '\0';
 890                 }
 891 
 892                 if ( gotstar == 0 ) {
 893                         ftype = LDAP_SUBSTRING_INITIAL;
 894                 } else if ( nextstar == NULL ) {
 895                         ftype = LDAP_SUBSTRING_FINAL;
 896                 } else {
 897                         ftype = LDAP_SUBSTRING_ANY;
 898                 }
 899                 if ( *val != '\0' ) {
 900                         if (( len = unescape_filterval( val )) < 0 ||
 901                             ber_printf( ber, "to", ftype, val, len ) == -1 ) {
 902                                 return( -1 );
 903                         }
 904                 }
 905 
 906                 gotstar = 1;
 907         }
 908 
 909         if ( ber_printf( ber, "}}" ) == -1 ) {
 910                 return( -1 );
 911         }
 912 
 913         return( 0 );
 914 }
 915 
 916 int
 917 LDAP_CALL
 918 ldap_search_st(
 919     LDAP                *ld, 
 920     const char          *base, 
 921     int                 scope, 
 922     const char          *filter, 
 923     char                **attrs,
 924     int                 attrsonly, 
 925     struct timeval      *timeout, 
 926     LDAPMessage         **res
 927 )
 928 {
 929         return( nsldapi_search_s( ld, base, scope, filter, attrs, attrsonly,
 930             NULL, NULL, timeout, -1, -1, res ));
 931 }
 932 
 933 int
 934 LDAP_CALL
 935 ldap_search_s(
 936     LDAP        *ld, 
 937     const char  *base, 
 938     int         scope, 
 939     const char  *filter, 
 940     char        **attrs,
 941     int         attrsonly, 
 942     LDAPMessage **res
 943 )
 944 {
 945         return( nsldapi_search_s( ld, base, scope, filter, attrs, attrsonly,
 946             NULL, NULL, NULL, -1, -1, res ));
 947 }
 948 
 949 int LDAP_CALL
 950 ldap_search_ext_s(
 951     LDAP                *ld, 
 952     const char          *base, 
 953     int                 scope, 
 954     const char          *filter, 
 955     char                **attrs,
 956     int                 attrsonly, 
 957     LDAPControl         **serverctrls,
 958     LDAPControl         **clientctrls,
 959     struct timeval      *timeoutp,
 960     int                 sizelimit,
 961     LDAPMessage         **res
 962 )
 963 {
 964         return( nsldapi_search_s( ld, base, scope, filter, attrs, attrsonly,
 965             serverctrls, clientctrls, timeoutp,
 966             nsldapi_timeval2ldaplimit( timeoutp, -1 ), sizelimit, res ));
 967 }
 968 
 969 
 970 static int 
 971 nsldapi_search_s(
 972     LDAP                *ld, 
 973     const char          *base, 
 974     int                 scope, 
 975     const char          *filter, 
 976     char                **attrs,
 977     int                 attrsonly, 
 978     LDAPControl         **serverctrls,
 979     LDAPControl         **clientctrls,
 980     struct timeval      *localtimeoutp,
 981     int                 timelimit,      /* -1 means use ld->ld_timelimit */
 982     int                 sizelimit,      /* -1 means use ld->ld_sizelimit */
 983     LDAPMessage         **res
 984 )
 985 {
 986         int     err, msgid;
 987 
 988         /*
 989          * It is an error to pass in a zero'd timeval.
 990          */
 991         if ( localtimeoutp != NULL && localtimeoutp->tv_sec == 0 &&
 992             localtimeoutp->tv_usec == 0 ) {
 993                 if ( ld != NULL ) {
 994                         LDAP_SET_LDERRNO( ld, LDAP_PARAM_ERROR, NULL, NULL );
 995                 }
 996                 if ( res != NULL ) {
 997                         *res = NULL;
 998                 }
 999                 return( LDAP_PARAM_ERROR );
1000         }
1001 
1002         if (( err = nsldapi_search( ld, base, scope, filter, attrs, attrsonly,
1003             serverctrls, clientctrls, timelimit, sizelimit, &msgid ))
1004             != LDAP_SUCCESS ) {
1005                 if ( res != NULL ) {
1006                         *res = NULL;
1007                 }
1008                 return( err );
1009         }
1010 
1011         if ( ldap_result( ld, msgid, 1, localtimeoutp, res ) == -1 ) {
1012                 /*
1013                  * Error.  ldap_result() sets *res to NULL for us.
1014                  */
1015                 return( LDAP_GET_LDERRNO( ld, NULL, NULL ) );
1016         }
1017 
1018         if ( LDAP_GET_LDERRNO( ld, NULL, NULL ) == LDAP_TIMEOUT ) {
1019                 (void) ldap_abandon( ld, msgid );
1020                 err = LDAP_TIMEOUT;
1021                 LDAP_SET_LDERRNO( ld, err, NULL, NULL );
1022                 if ( res != NULL ) {
1023                         *res = NULL;
1024                 }
1025                 return( err );
1026         }
1027 
1028         return( ldap_result2error( ld, *res, 0 ) );
1029 }