root/usr/src/lib/libldap5/sources/ldap/common/getfilter.c
/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */
/*
 *  Copyright (c) 1993 Regents of the University of Michigan.
 *  All rights reserved.
 */
/*
 *  getfilter.c -- optional add-on to libldap
 */

#if 0
#ifndef lint
static char copyright[] = "@(#) Copyright (c) 1993 Regents of the University of Michigan.\nAll rights reserved.\n";
#endif
#endif

#include "ldap-int.h"
#include "regex.h"
#include <stdio.h> /* sprintf */

static int break_into_words( char *str, char *delims, char ***wordsp );

#if !defined( macintosh ) && !defined( DOS )
extern char     * LDAP_CALL re_comp();
extern int      LDAP_CALL re_exec( char *lp );
#endif

#define FILT_MAX_LINE_LEN       1024

LDAPFiltDesc *
LDAP_CALL
ldap_init_getfilter( char *fname )
{
    FILE                *fp;
    char                *buf;
    ssize_t             rlen, len;
    int                 eof;
    LDAPFiltDesc        *lfdp;

    if (( fp = fopen( fname, "rF" )) == NULL ) {
        return( NULL );
    }

    if ( fseek( fp, 0L, SEEK_END ) != 0 ) {     /* move to end to get len */
        fclose( fp );
        return( NULL );
    }

    len = ftell( fp );

    if ( fseek( fp, 0L, SEEK_SET ) != 0 ) {     /* back to start of file */
        fclose( fp );
        return( NULL );
    }

    if (( buf = NSLDAPI_MALLOC( (size_t)len )) == NULL ) {
        fclose( fp );
        return( NULL );
    }

    rlen = fread( buf, 1, (size_t)len, fp );
    eof = feof( fp );
    fclose( fp );

    if ( rlen != len && !eof ) {        /* error:  didn't get the whole file */
        NSLDAPI_FREE( buf );
        return( NULL );
    }


    lfdp = ldap_init_getfilter_buf( buf, rlen );
    NSLDAPI_FREE( buf );

    return( lfdp );
}


LDAPFiltDesc *
LDAP_CALL
ldap_init_getfilter_buf( char *buf, ssize_t buflen )
{
    LDAPFiltDesc        *lfdp;
    LDAPFiltList        *flp, *nextflp;
    LDAPFiltInfo        *fip, *nextfip;
    char                *tag, **tok;
    int                 tokcnt, i;
    long                buffer_len = (long)buflen;

    if ( (buf == NULL) || (buflen < 0) ||
         ( lfdp = (LDAPFiltDesc *)NSLDAPI_CALLOC(1, sizeof( LDAPFiltDesc)))
         == NULL ) {
        return( NULL );
    }

    flp = nextflp = NULL;
    fip = NULL;
    tag = NULL;

    while ( buflen > 0 && ( tokcnt = ldap_next_line_tokens( &buf,
                &buffer_len, &tok )) > 0 ) {
        switch( tokcnt ) {
        case 1:         /* tag line */
            if ( tag != NULL ) {
                NSLDAPI_FREE( tag );
            }
            tag = tok[ 0 ];
            NSLDAPI_FREE( tok );
            break;
        case 4:
        case 5:         /* start of filter info. list */
            if (( nextflp = (LDAPFiltList *)NSLDAPI_CALLOC( 1,
                    sizeof( LDAPFiltList ))) == NULL ) {
                ldap_getfilter_free( lfdp );
                return( NULL );
            }
            nextflp->lfl_tag = nsldapi_strdup( tag );
            nextflp->lfl_pattern = tok[ 0 ];
            if ( re_comp( nextflp->lfl_pattern ) != NULL ) {
                char    msg[256];
                ldap_getfilter_free( lfdp );
                sprintf( msg, dgettext(TEXT_DOMAIN,
                        "bad regular expresssion %s\n"),
                        nextflp->lfl_pattern );
                ber_err_print( msg );
                ldap_free_strarray( tok );
                return( NULL );
            }

            nextflp->lfl_delims = tok[ 1 ];
            nextflp->lfl_ilist = NULL;
            nextflp->lfl_next = NULL;
            if ( flp == NULL ) {        /* first one */
                lfdp->lfd_filtlist = nextflp;
            } else {
                flp->lfl_next = nextflp;
            }
            flp = nextflp;
            fip = NULL;
            for ( i = 2; i < 5; ++i ) {
                tok[ i - 2 ] = tok[ i ];
            }
            /* fall through */

        case 2:
        case 3:         /* filter, desc, and optional search scope */
            if ( nextflp != NULL ) { /* add to info list */
                if (( nextfip = (LDAPFiltInfo *)NSLDAPI_CALLOC( 1,
                        sizeof( LDAPFiltInfo ))) == NULL ) {
                    ldap_getfilter_free( lfdp );
                    ldap_free_strarray( tok );
                    return( NULL );
                }
                if ( fip == NULL ) {    /* first one */
                    nextflp->lfl_ilist = nextfip;
                } else {
                    fip->lfi_next = nextfip;
                }
                fip = nextfip;
                nextfip->lfi_next = NULL;
                nextfip->lfi_filter = tok[ 0 ];
                nextfip->lfi_desc = tok[ 1 ];
                if ( tok[ 2 ] != NULL ) {
                    if ( strcasecmp( tok[ 2 ], "subtree" ) == 0 ) {
                        nextfip->lfi_scope = LDAP_SCOPE_SUBTREE;
                    } else if ( strcasecmp( tok[ 2 ], "onelevel" ) == 0 ) {
                        nextfip->lfi_scope = LDAP_SCOPE_ONELEVEL;
                    } else if ( strcasecmp( tok[ 2 ], "base" ) == 0 ) {
                        nextfip->lfi_scope = LDAP_SCOPE_BASE;
                    } else {
                        ldap_free_strarray( tok );
                        ldap_getfilter_free( lfdp );
                        return( NULL );
                    }
                    NSLDAPI_FREE( tok[ 2 ] );
                    tok[ 2 ] = NULL;
                } else {
                    nextfip->lfi_scope = LDAP_SCOPE_SUBTREE;    /* default */
                }
                nextfip->lfi_isexact = ( strchr( tok[ 0 ], '*' ) == NULL &&
                        strchr( tok[ 0 ], '~' ) == NULL );
                NSLDAPI_FREE( tok );
            }
            break;

        default:
            ldap_free_strarray( tok );
            ldap_getfilter_free( lfdp );
            return( NULL );
        }
    }

    if ( tag != NULL ) {
        NSLDAPI_FREE( tag );
    }

    return( lfdp );
}


int
LDAP_CALL
ldap_set_filter_additions( LDAPFiltDesc *lfdp, char *prefix, char *suffix )
{
    if ( lfdp == NULL ) {
        return( LDAP_PARAM_ERROR );
    }

    if ( lfdp->lfd_filtprefix != NULL ) {
        NSLDAPI_FREE( lfdp->lfd_filtprefix );
    }
    lfdp->lfd_filtprefix = ( prefix == NULL ) ? NULL : nsldapi_strdup( prefix );

    if ( lfdp->lfd_filtsuffix != NULL ) {
        NSLDAPI_FREE( lfdp->lfd_filtsuffix );
    }
    lfdp->lfd_filtsuffix = ( suffix == NULL ) ? NULL : nsldapi_strdup( suffix );

    return( LDAP_SUCCESS );
}


/*
 * ldap_setfilteraffixes() is deprecated -- use ldap_set_filter_additions()
 */
void
LDAP_CALL
ldap_setfilteraffixes( LDAPFiltDesc *lfdp, char *prefix, char *suffix )
{
    (void)ldap_set_filter_additions( lfdp, prefix, suffix );
}


LDAPFiltInfo *
LDAP_CALL
ldap_getfirstfilter( LDAPFiltDesc *lfdp, char *tagpat, char *value )
{
    LDAPFiltList        *flp;

    if ( lfdp == NULL || tagpat == NULL || value == NULL ) {
        return( NULL ); /* punt */
    }

    if ( lfdp->lfd_curvalcopy != NULL ) {
        NSLDAPI_FREE( lfdp->lfd_curvalcopy );
        NSLDAPI_FREE( lfdp->lfd_curvalwords );
    }

    lfdp->lfd_curval = value;
    lfdp->lfd_curfip = NULL;

    for ( flp = lfdp->lfd_filtlist; flp != NULL; flp = flp->lfl_next ) {
        if ( re_comp( tagpat ) == NULL && re_exec( flp->lfl_tag ) == 1
                && re_comp( flp->lfl_pattern ) == NULL
                && re_exec( lfdp->lfd_curval ) == 1 ) {
            lfdp->lfd_curfip = flp->lfl_ilist;
            break;
        }
    }

    if ( lfdp->lfd_curfip == NULL ) {
        return( NULL );
    }

    if (( lfdp->lfd_curvalcopy = nsldapi_strdup( value )) == NULL ) {
        return( NULL );
    }

    if ( break_into_words( lfdp->lfd_curvalcopy, flp->lfl_delims,
                &lfdp->lfd_curvalwords ) < 0 ) {
        NSLDAPI_FREE( lfdp->lfd_curvalwords );
        lfdp->lfd_curvalwords = NULL;
        return( NULL );
    }

    return( ldap_getnextfilter( lfdp ));
}


LDAPFiltInfo *
LDAP_CALL
ldap_getnextfilter( LDAPFiltDesc *lfdp )
{
    LDAPFiltInfo        *fip;

    if ( lfdp == NULL || ( fip = lfdp->lfd_curfip ) == NULL ) {
        return( NULL );
    }

    lfdp->lfd_curfip = fip->lfi_next;

    ldap_build_filter( lfdp->lfd_filter, LDAP_FILT_MAXSIZ, fip->lfi_filter,
            lfdp->lfd_filtprefix, lfdp->lfd_filtsuffix, NULL,
            lfdp->lfd_curval, lfdp->lfd_curvalwords );
    lfdp->lfd_retfi.lfi_filter = lfdp->lfd_filter;
    lfdp->lfd_retfi.lfi_desc = fip->lfi_desc;
    lfdp->lfd_retfi.lfi_scope = fip->lfi_scope;
    lfdp->lfd_retfi.lfi_isexact = fip->lfi_isexact;

    return( &lfdp->lfd_retfi );
}


static char*
filter_add_strn( char *f, char *flimit, char *v, size_t vlen )
     /* Copy v into f.  If flimit is too small, return NULL;
      * otherwise return (f + vlen).
      */
{
    auto size_t flen = flimit - f;
    if ( vlen > flen ) { /* flimit is too small */
        if ( flen > 0 ) SAFEMEMCPY( f, v, flen );
        return NULL;
    }
    if ( vlen > 0 ) SAFEMEMCPY( f, v, vlen );
    return f + vlen;
}

static char*
filter_add_value( char *f, char *flimit, char *v, int escape_all )
     /* Copy v into f, but with parentheses escaped.  But only escape * and \
      * if escape_all is non-zero so that either "*" or "\2a" can be used in
      * v, with different meanings.
      * If flimit is too small, return NULL; otherwise
      * return (f + the number of bytes copied).
      */
{
    auto char x[4];
    auto size_t slen;
    while ( f && *v ) {
        switch ( *v ) {
        case '*':
            if ( escape_all ) {
                f = filter_add_strn( f, flimit, "\\2a", 3 );
                v++;
            } else {
                if ( f < flimit ) {
                    *f++ = *v++;
                } else {
                    f = NULL; /* overflow */
                }
            }
            break;

        case '(':
        case ')':
            sprintf( x, "\\%02x", (unsigned)*v );
            f = filter_add_strn( f, flimit, x, 3 );
            v++;
            break;

        case '\\':
            if ( escape_all ) {
                f = filter_add_strn( f, flimit, "\\5c", 3 );
                v++;
            } else {
                slen = (ldap_utf8isxdigit( v+1 ) &&
                        ldap_utf8isxdigit( v+2 )) ? 3 : (v[1] ? 2 : 1);
                f = filter_add_strn( f, flimit, v, slen );
                v += slen;
            }
            break;

        default:
            if ( f < flimit ) {
                *f++ = *v++;
            } else {
                f = NULL; /* overflow */
            }
            break;
        }
    }
    return f;
}

int
LDAP_CALL
ldap_create_filter( char *filtbuf, unsigned long buflen, char *pattern,
        char *prefix, char *suffix, char *attr, char *value, char **valwords )
{
        char    *p, *f, *flimit;
        int     i, wordcount, wordnum, endwordnum, escape_all;

    /*
     * there is some confusion on what to create for a filter if
     * attr or value are null pointers.  For now we just leave them
     * as TO BE DEALT with
     */

        if ( filtbuf == NULL || buflen == 0 || pattern == NULL ){
                return( LDAP_PARAM_ERROR );
        }

        if ( valwords == NULL ) {
            wordcount = 0;
        } else {
            for ( wordcount = 0; valwords[ wordcount ] != NULL; ++wordcount ) {
                ;
            }
        }

        f = filtbuf;
        flimit = filtbuf + buflen - 1;

        if ( prefix != NULL ) {
            f = filter_add_strn( f, flimit, prefix, strlen( prefix ));
        }

        for ( p = pattern; f != NULL && *p != '\0'; ++p ) {
            if ( *p == '%' ) {
                ++p;
                if ( *p == 'v' || *p == 'e' ) {
                    escape_all = ( *p == 'e' );
                    if ( ldap_utf8isdigit( p+1 )) {
                        ++p;
                        wordnum = *p - '1';
                        if ( *(p+1) == '-' ) {
                            ++p;
                            if ( ldap_utf8isdigit( p+1 )) {
                                ++p;
                                endwordnum = *p - '1';  /* e.g., "%v2-4" */
                                if ( endwordnum > wordcount - 1 ) {
                                    endwordnum = wordcount - 1;
                                }
                            } else {
                                endwordnum = wordcount - 1;  /* e.g., "%v2-" */
                            }
                        } else {
                            endwordnum = wordnum;       /* e.g., "%v2" */
                        }

                        if ( wordcount > 0 ) {
                            for ( i = wordnum; i <= endwordnum; ++i ) {
                                if ( i > wordnum ) {  /* add blank btw words */
                                    f = filter_add_strn( f, flimit, " ", 1 );
                                    if ( f == NULL ) break;
                                }
                                f = filter_add_value( f, flimit, valwords[ i ],
                                        escape_all );
                                if ( f == NULL ) break;
                            }
                        }
                    } else if ( *(p+1) == '$' ) {
                        ++p;
                        if ( wordcount > 0 ) {
                            wordnum = wordcount - 1;
                            f = filter_add_value( f, flimit,
                                    valwords[ wordnum ], escape_all );
                        }
                    } else if ( value != NULL ) {
                        f = filter_add_value( f, flimit, value, escape_all );
                    }
                } else if ( *p == 'a' && attr != NULL ) {
                    f = filter_add_strn( f, flimit, attr, strlen( attr ));
                } else {
                    *f++ = *p;
                }
            } else {
                *f++ = *p;
            }
            if ( f > flimit ) { /* overflow */
                f = NULL;
            }
        }

        if ( suffix != NULL && f != NULL) {
            f = filter_add_strn( f, flimit, suffix, strlen( suffix ));
        }

        if ( f == NULL ) {
            *flimit = '\0';
            return( LDAP_SIZELIMIT_EXCEEDED );
        }
        *f = '\0';
        return( LDAP_SUCCESS );
}


/*
 * ldap_build_filter() is deprecated -- use ldap_create_filter() instead
 */
void
LDAP_CALL
ldap_build_filter( char *filtbuf, size_t buflen, char *pattern,
        char *prefix, char *suffix, char *attr, char *value, char **valwords )
{
    (void)ldap_create_filter( filtbuf, buflen, pattern, prefix, suffix, attr,
            value, valwords );
}


static int
break_into_words( char *str, char *delims, char ***wordsp )
{
    char        *word, **words;
    int         count;
    char        *lasts;

    if (( words = (char **)NSLDAPI_CALLOC( 1, sizeof( char * ))) == NULL ) {
        return( -1 );
    }
    count = 0;
    words[ count ] = NULL;

    word = ldap_utf8strtok_r( str, delims, &lasts );
    while ( word != NULL ) {
        if (( words = (char **)NSLDAPI_REALLOC( words,
                ( count + 2 ) * sizeof( char * ))) == NULL ) {
            return( -1 );
        }

        words[ count ] = word;
        words[ ++count ] = NULL;
        word = ldap_utf8strtok_r( NULL, delims, &lasts );
    }

    *wordsp = words;
    return( count );
}