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

/*
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is Mozilla Communicator client code, released
 * March 31, 1998.
 *
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation. Portions created by Netscape are
 * Copyright (C) 1998-1999 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s):
 */
/*
 * Copyright (c) 1993, 1994 Regents of the University of Michigan.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that this notice is preserved and that due credit is given
 * to the University of Michigan at Ann Arbor. The name of the University
 * may not be used to endorse or promote products derived from this
 * software without specific prior written permission. This software
 * is provided ``as is'' without express or implied warranty.
 */
/*
 * disptmpl.c:  display template library routines for LDAP clients
 */

#include "ldap-int.h"
#include "disptmpl.h"

static void free_disptmpl( struct ldap_disptmpl *tmpl );
static int read_next_tmpl( char **bufp, long *blenp,
        struct ldap_disptmpl **tmplp, int dtversion );

static char             *tmploptions[] = {
    "addable", "modrdn",
    "altview",
    NULL
};


static unsigned long    tmploptvals[] = {
    LDAP_DTMPL_OPT_ADDABLE, LDAP_DTMPL_OPT_ALLOWMODRDN,
    LDAP_DTMPL_OPT_ALTVIEW,
};


static char             *itemtypes[] = {
    "cis",                      "mls",                  "dn",
    "bool",                     "jpeg",                 "jpegbtn",
    "fax",                      "faxbtn",               "audiobtn",
    "time",                     "date",                 "url",
    "searchact",                "linkact",              "adddnact",
    "addact",                   "verifyact",            "mail",
    NULL
};

static unsigned long    itemsynids[] = {
    LDAP_SYN_CASEIGNORESTR,     LDAP_SYN_MULTILINESTR,  LDAP_SYN_DN,
    LDAP_SYN_BOOLEAN,           LDAP_SYN_JPEGIMAGE,     LDAP_SYN_JPEGBUTTON,
    LDAP_SYN_FAXIMAGE,          LDAP_SYN_FAXBUTTON,     LDAP_SYN_AUDIOBUTTON,
    LDAP_SYN_TIME,              LDAP_SYN_DATE,          LDAP_SYN_LABELEDURL,
    LDAP_SYN_SEARCHACTION,      LDAP_SYN_LINKACTION,    LDAP_SYN_ADDDNACTION,
    LDAP_SYN_ADDDNACTION,       LDAP_SYN_VERIFYDNACTION,LDAP_SYN_RFC822ADDR,
};


static char             *itemoptions[] = {
    "ro",                               "sort",
    "1val",                             "hide",
    "required",                         "hideiffalse",
    NULL
};


static unsigned long    itemoptvals[] = {
    LDAP_DITEM_OPT_READONLY,            LDAP_DITEM_OPT_SORTVALUES,
    LDAP_DITEM_OPT_SINGLEVALUED,        LDAP_DITEM_OPT_HIDEIFEMPTY,
    LDAP_DITEM_OPT_VALUEREQUIRED,       LDAP_DITEM_OPT_HIDEIFFALSE,
};


#define ADDEF_CONSTANT  "constant"
#define ADDEF_ADDERSDN  "addersdn"


int
LDAP_CALL
ldap_init_templates( char *file, struct ldap_disptmpl **tmpllistp )
{
    FILE        *fp;
    char        *buf;
    long        rlen, len;
    int         rc, eof;

    *tmpllistp = NULLDISPTMPL;

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

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

    len = ftell( fp );

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

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

    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( LDAP_TMPL_ERR_FILE );
    }

    rc = ldap_init_templates_buf( buf, rlen, tmpllistp );
    NSLDAPI_FREE( buf );

    return( rc );
}


int
LDAP_CALL
ldap_init_templates_buf( char *buf, long buflen,
        struct ldap_disptmpl **tmpllistp )
{
    int                         rc = 0, version;
    char                        **toks;
    struct ldap_disptmpl        *prevtmpl, *tmpl;

    *tmpllistp = prevtmpl = NULLDISPTMPL;

    if ( ldap_next_line_tokens( &buf, &buflen, &toks ) != 2 ||
            strcasecmp( toks[ 0 ], "version" ) != 0 ) {
        ldap_free_strarray( toks );
        return( LDAP_TMPL_ERR_SYNTAX );
    }
    version = atoi( toks[ 1 ] );
    ldap_free_strarray( toks );
    if ( version != LDAP_TEMPLATE_VERSION ) {
        return( LDAP_TMPL_ERR_VERSION );
    }

    while ( buflen > 0 && ( rc = read_next_tmpl( &buf, &buflen, &tmpl,
            version )) == 0 && tmpl != NULLDISPTMPL ) {
        if ( prevtmpl == NULLDISPTMPL ) {
            *tmpllistp = tmpl;
        } else {
            prevtmpl->dt_next = tmpl;
        }
        prevtmpl = tmpl;
    }

    if ( rc != 0 ) {
        ldap_free_templates( *tmpllistp );
    }

    return( rc );
}



void
LDAP_CALL
ldap_free_templates( struct ldap_disptmpl *tmpllist )
{
    struct ldap_disptmpl        *tp, *nexttp;

    if ( tmpllist != NULL ) {
        for ( tp = tmpllist; tp != NULL; tp = nexttp ) {
            nexttp = tp->dt_next;
            free_disptmpl( tp );
        }
    }
}


static void
free_disptmpl( struct ldap_disptmpl *tmpl )
{
    if ( tmpl != NULL ) {
        if ( tmpl->dt_name != NULL ) {
            NSLDAPI_FREE(  tmpl->dt_name );
        }

        if ( tmpl->dt_pluralname != NULL ) {
            NSLDAPI_FREE( tmpl->dt_pluralname );
        }

        if ( tmpl->dt_iconname != NULL ) {
            NSLDAPI_FREE( tmpl->dt_iconname );
        }

        if ( tmpl->dt_authattrname != NULL ) {
            NSLDAPI_FREE( tmpl->dt_authattrname );
        }

        if ( tmpl->dt_defrdnattrname != NULL ) {
            NSLDAPI_FREE( tmpl->dt_defrdnattrname );
        }

        if ( tmpl->dt_defaddlocation != NULL ) {
            NSLDAPI_FREE( tmpl->dt_defaddlocation );
        }

        if (  tmpl->dt_oclist != NULL ) {
            struct ldap_oclist  *ocp, *nextocp;

            for ( ocp = tmpl->dt_oclist; ocp != NULL; ocp = nextocp ) {
                nextocp = ocp->oc_next;
                ldap_free_strarray( ocp->oc_objclasses );
                NSLDAPI_FREE( ocp );
            }
        }

        if (  tmpl->dt_adddeflist != NULL ) {
            struct ldap_adddeflist      *adp, *nextadp;

            for ( adp = tmpl->dt_adddeflist; adp != NULL; adp = nextadp ) {
                nextadp = adp->ad_next;
                if( adp->ad_attrname != NULL ) {
                    NSLDAPI_FREE( adp->ad_attrname );
                }
                if( adp->ad_value != NULL ) {
                    NSLDAPI_FREE( adp->ad_value );
                }
                NSLDAPI_FREE( adp );
            }
        }

        if (  tmpl->dt_items != NULL ) {
            struct ldap_tmplitem        *rowp, *nextrowp, *colp, *nextcolp;

            for ( rowp = tmpl->dt_items; rowp != NULL; rowp = nextrowp ) {
                nextrowp = rowp->ti_next_in_col;
                for ( colp = rowp; colp != NULL; colp = nextcolp ) {
                    nextcolp = colp->ti_next_in_row;
                    if ( colp->ti_attrname != NULL ) {
                        NSLDAPI_FREE( colp->ti_attrname );
                    }
                    if ( colp->ti_label != NULL ) {
                        NSLDAPI_FREE( colp->ti_label );
                    }
                    if ( colp->ti_args != NULL ) {
                        ldap_free_strarray( colp->ti_args );
                    }
                    NSLDAPI_FREE( colp );
                }
            }
        }

        NSLDAPI_FREE( tmpl );
    }
}


struct ldap_disptmpl *
LDAP_CALL
ldap_first_disptmpl( struct ldap_disptmpl *tmpllist )
{
    return( tmpllist );
}


struct ldap_disptmpl *
LDAP_CALL
ldap_next_disptmpl( struct ldap_disptmpl *tmpllist,
        struct ldap_disptmpl *tmpl )
{
    return( tmpl == NULLDISPTMPL ? tmpl : tmpl->dt_next );
}


struct ldap_disptmpl *
LDAP_CALL
ldap_name2template( char *name, struct ldap_disptmpl *tmpllist )
{
    struct ldap_disptmpl        *dtp;

    for ( dtp = ldap_first_disptmpl( tmpllist ); dtp != NULLDISPTMPL;
            dtp = ldap_next_disptmpl( tmpllist, dtp )) {
        if ( strcasecmp( name, dtp->dt_name ) == 0 ) {
            return( dtp );
        }
    }

    return( NULLDISPTMPL );
}


struct ldap_disptmpl *
LDAP_CALL
ldap_oc2template( char **oclist, struct ldap_disptmpl *tmpllist )
{
    struct ldap_disptmpl        *dtp;
    struct ldap_oclist          *oclp;
    int                         i, j, needcnt, matchcnt;

    if ( tmpllist == NULL || oclist == NULL || oclist[ 0 ] == NULL ) {
        return( NULLDISPTMPL );
    }

    for ( dtp = ldap_first_disptmpl( tmpllist ); dtp != NULLDISPTMPL;
                dtp = ldap_next_disptmpl( tmpllist, dtp )) {
        for ( oclp = dtp->dt_oclist; oclp != NULLOCLIST;
                oclp = oclp->oc_next ) {
            needcnt = matchcnt = 0;
            for ( i = 0; oclp->oc_objclasses[ i ] != NULL; ++i ) {
                for ( j = 0; oclist[ j ] != NULL; ++j ) {
                    if ( strcasecmp( oclist[ j ], oclp->oc_objclasses[ i ] )
                            == 0 ) {
                        ++matchcnt;
                    }
                }
                ++needcnt;
            }

            if ( matchcnt == needcnt ) {
                return( dtp );
            }
        }
    }

    return( NULLDISPTMPL );
}


struct ldap_tmplitem *
LDAP_CALL
ldap_first_tmplrow( struct ldap_disptmpl *tmpl )
{
    return( tmpl->dt_items );
}


struct ldap_tmplitem *
LDAP_CALL
ldap_next_tmplrow( struct ldap_disptmpl *tmpl, struct ldap_tmplitem *row )
{
    return( row == NULLTMPLITEM ? row : row->ti_next_in_col );
}


struct ldap_tmplitem *
LDAP_CALL
ldap_first_tmplcol( struct ldap_disptmpl *tmpl, struct ldap_tmplitem *row )
{
    return( row );
}


struct ldap_tmplitem *
LDAP_CALL
ldap_next_tmplcol( struct ldap_disptmpl *tmpl, struct ldap_tmplitem *row,
        struct ldap_tmplitem *col )
{
    return( col == NULLTMPLITEM ? col : col->ti_next_in_row );
}


char **
LDAP_CALL
ldap_tmplattrs( struct ldap_disptmpl *tmpl, char **includeattrs,
        int exclude, unsigned long syntaxmask )
{
/*
 * this routine should filter out duplicate attributes...
 */
    struct ldap_tmplitem        *tirowp, *ticolp;
    int                 i, attrcnt, memerr;
    char                **attrs;

    attrcnt = 0;
    memerr = 0;

    if (( attrs = (char **)NSLDAPI_MALLOC( sizeof( char * ))) == NULL ) {
        return( NULL );
    }

    if ( includeattrs != NULL ) {
        for ( i = 0; !memerr && includeattrs[ i ] != NULL; ++i ) {
            if (( attrs = (char **)NSLDAPI_REALLOC( attrs, ( attrcnt + 2 ) *
                    sizeof( char * ))) == NULL || ( attrs[ attrcnt++ ] =
                    nsldapi_strdup( includeattrs[ i ] )) == NULL ) {
                memerr = 1;
            } else {
                attrs[ attrcnt ] = NULL;
            }
        }
    }

    for ( tirowp = ldap_first_tmplrow( tmpl );
            !memerr && tirowp != NULLTMPLITEM;
            tirowp = ldap_next_tmplrow( tmpl, tirowp )) {
        for ( ticolp = ldap_first_tmplcol( tmpl, tirowp );
                ticolp != NULLTMPLITEM;
                ticolp = ldap_next_tmplcol( tmpl, tirowp, ticolp )) {

            if ( syntaxmask != 0 ) {
                if (( exclude &&
                        ( syntaxmask & ticolp->ti_syntaxid ) != 0 ) ||
                        ( !exclude &&
                        ( syntaxmask & ticolp->ti_syntaxid ) == 0 )) {
                    continue;
                }
            }

            if ( ticolp->ti_attrname != NULL ) {
                if (( attrs = (char **)NSLDAPI_REALLOC( attrs, ( attrcnt + 2 )
                        * sizeof( char * ))) == NULL || ( attrs[ attrcnt++ ] =
                        nsldapi_strdup( ticolp->ti_attrname )) == NULL ) {
                    memerr = 1;
                } else {
                    attrs[ attrcnt ] = NULL;
                }
            }
        }
    }

    if ( memerr || attrcnt == 0 ) {
        for ( i = 0; i < attrcnt; ++i ) {
            if ( attrs[ i ] != NULL ) {
                NSLDAPI_FREE( attrs[ i ] );
            }
        }

        NSLDAPI_FREE( (char *)attrs );
        return( NULL );
    }

    return( attrs );
}


static int
read_next_tmpl( char **bufp, long *blenp, struct ldap_disptmpl **tmplp,
        int dtversion )
{
    int                         i, j, tokcnt, samerow, adsource;
    char                        **toks, *itemopts;
    struct ldap_disptmpl        *tmpl = NULL;
    struct ldap_oclist          *ocp = NULL, *prevocp = NULL;
    struct ldap_adddeflist      *adp = NULL, *prevadp = NULL;
    struct ldap_tmplitem        *rowp = NULL, *ip = NULL, *previp = NULL;

    /*
     * template name comes first
     */
    if (( tokcnt = ldap_next_line_tokens( bufp, blenp, &toks )) != 1 ) {
        ldap_free_strarray( toks );
        return( tokcnt == 0 ? 0 : LDAP_TMPL_ERR_SYNTAX );
    }

    if (( tmpl = (struct ldap_disptmpl *)NSLDAPI_CALLOC( 1,
            sizeof( struct ldap_disptmpl ))) == NULL ) {
        ldap_free_strarray( toks );
        return(  LDAP_TMPL_ERR_MEM );
    }
    tmpl->dt_name = toks[ 0 ];
    NSLDAPI_FREE( (char *)toks );

    /*
     * template plural name comes next
     */
    if (( tokcnt = ldap_next_line_tokens( bufp, blenp, &toks )) != 1 ) {
        ldap_free_strarray( toks );
        free_disptmpl( tmpl );
        return( LDAP_TMPL_ERR_SYNTAX );
    }
    tmpl->dt_pluralname = toks[ 0 ];
    NSLDAPI_FREE( (char *)toks );

    /*
     * template icon name is next
     */
    if (( tokcnt = ldap_next_line_tokens( bufp, blenp, &toks )) != 1 ) {
        ldap_free_strarray( toks );
        free_disptmpl( tmpl );
        return( LDAP_TMPL_ERR_SYNTAX );
    }
    tmpl->dt_iconname = toks[ 0 ];
    NSLDAPI_FREE( (char *)toks );

    /*
     * template options come next
     */
    if (( tokcnt = ldap_next_line_tokens( bufp, blenp, &toks )) < 1 ) {
        ldap_free_strarray( toks );
        free_disptmpl( tmpl );
        return( LDAP_TMPL_ERR_SYNTAX );
    }
    for ( i = 0; toks[ i ] != NULL; ++i ) {
        for ( j = 0; tmploptions[ j ] != NULL; ++j ) {
            if ( strcasecmp( toks[ i ], tmploptions[ j ] ) == 0 ) {
                tmpl->dt_options |= tmploptvals[ j ];
            }
        }
    }
    ldap_free_strarray( toks );

    /*
     * object class list is next
     */
    while (( tokcnt = ldap_next_line_tokens( bufp, blenp, &toks )) > 0 ) {
        if (( ocp = (struct ldap_oclist *)NSLDAPI_CALLOC( 1,
                sizeof( struct ldap_oclist ))) == NULL ) {
            ldap_free_strarray( toks );
            free_disptmpl( tmpl );
            return( LDAP_TMPL_ERR_MEM );
        }
        ocp->oc_objclasses = toks;
        if ( tmpl->dt_oclist == NULL ) {
            tmpl->dt_oclist = ocp;
        } else {
            prevocp->oc_next = ocp;
        }
        prevocp = ocp;
    }
    if ( tokcnt < 0 ) {
        free_disptmpl( tmpl );
        return( LDAP_TMPL_ERR_SYNTAX );
    }

    /*
     * read name of attribute to authenticate as
     */
    if (( tokcnt = ldap_next_line_tokens( bufp, blenp, &toks )) != 1 ) {
        ldap_free_strarray( toks );
        free_disptmpl( tmpl );
        return( LDAP_TMPL_ERR_SYNTAX );
    }
    if ( toks[ 0 ][ 0 ] != '\0' ) {
        tmpl->dt_authattrname = toks[ 0 ];
    } else {
        NSLDAPI_FREE( toks[ 0 ] );
    }
    NSLDAPI_FREE( (char *)toks );

    /*
     * read default attribute to use for RDN
     */
    if (( tokcnt = ldap_next_line_tokens( bufp, blenp, &toks )) != 1 ) {
        ldap_free_strarray( toks );
        free_disptmpl( tmpl );
        return( LDAP_TMPL_ERR_SYNTAX );
    }
    tmpl->dt_defrdnattrname = toks[ 0 ];
    NSLDAPI_FREE( (char *)toks );

    /*
     * read default location for new entries
     */
    if (( tokcnt = ldap_next_line_tokens( bufp, blenp, &toks )) != 1 ) {
        ldap_free_strarray( toks );
        free_disptmpl( tmpl );
        return( LDAP_TMPL_ERR_SYNTAX );
    }
    if ( toks[ 0 ][ 0 ] != '\0' ) {
        tmpl->dt_defaddlocation = toks[ 0 ];
    } else {
        NSLDAPI_FREE( toks[ 0 ] );
    }
    NSLDAPI_FREE( (char *)toks );

    /*
     * read list of rules used to define default values for new entries
     */
    while (( tokcnt = ldap_next_line_tokens( bufp, blenp, &toks )) > 0 ) {
        if ( strcasecmp( ADDEF_CONSTANT, toks[ 0 ] ) == 0 ) {
            adsource = LDAP_ADSRC_CONSTANTVALUE;
        } else if ( strcasecmp( ADDEF_ADDERSDN, toks[ 0 ] ) == 0 ) {
            adsource = LDAP_ADSRC_ADDERSDN;
        } else {
            adsource = 0;
        }
        if ( adsource == 0 || tokcnt < 2 ||
                ( adsource == LDAP_ADSRC_CONSTANTVALUE && tokcnt != 3 ) ||
                ( adsource == LDAP_ADSRC_ADDERSDN && tokcnt != 2 )) {
            ldap_free_strarray( toks );
            free_disptmpl( tmpl );
            return( LDAP_TMPL_ERR_SYNTAX );
        }

        if (( adp = (struct ldap_adddeflist *)NSLDAPI_CALLOC( 1,
                sizeof( struct ldap_adddeflist ))) == NULL ) {
            ldap_free_strarray( toks );
            free_disptmpl( tmpl );
            return( LDAP_TMPL_ERR_MEM );
        }
        adp->ad_source = adsource;
        adp->ad_attrname = toks[ 1 ];
        if ( adsource == LDAP_ADSRC_CONSTANTVALUE ) {
            adp->ad_value = toks[ 2 ];
        }
        NSLDAPI_FREE( toks[ 0 ] );
        NSLDAPI_FREE( (char *)toks );

        if ( tmpl->dt_adddeflist == NULL ) {
            tmpl->dt_adddeflist = adp;
        } else {
            prevadp->ad_next = adp;
        }
        prevadp = adp;
    }

    /*
     * item list is next
     */
    samerow = 0;
    while (( tokcnt = ldap_next_line_tokens( bufp, blenp, &toks )) > 0 ) {
        if ( strcasecmp( toks[ 0 ], "item" ) == 0 ) {
            if ( tokcnt < 4 ) {
                ldap_free_strarray( toks );
                free_disptmpl( tmpl );
                return( LDAP_TMPL_ERR_SYNTAX );
            }

            if (( ip = (struct ldap_tmplitem *)NSLDAPI_CALLOC( 1,
                    sizeof( struct ldap_tmplitem ))) == NULL ) {
                ldap_free_strarray( toks );
                free_disptmpl( tmpl );
                return( LDAP_TMPL_ERR_MEM );
            }

            /*
             * find syntaxid from config file string
             */
            while (( itemopts = strrchr( toks[ 1 ], ',' )) != NULL ) {
                *itemopts++ = '\0';
                for ( i = 0; itemoptions[ i ] != NULL; ++i ) {
                    if ( strcasecmp( itemopts, itemoptions[ i ] ) == 0 ) {
                        break;
                    }
                }
                if ( itemoptions[ i ] == NULL ) {
                    ldap_free_strarray( toks );
                    free_disptmpl( tmpl );
                    return( LDAP_TMPL_ERR_SYNTAX );
                }
                ip->ti_options |= itemoptvals[ i ];
            }

            for ( i = 0; itemtypes[ i ] != NULL; ++i ) {
                if ( strcasecmp( toks[ 1 ], itemtypes[ i ] ) == 0 ) {
                    break;
                }
            }
            if ( itemtypes[ i ] == NULL ) {
                ldap_free_strarray( toks );
                free_disptmpl( tmpl );
                return( LDAP_TMPL_ERR_SYNTAX );
            }

            NSLDAPI_FREE( toks[ 0 ] );
            NSLDAPI_FREE( toks[ 1 ] );
            ip->ti_syntaxid = itemsynids[ i ];
            ip->ti_label = toks[ 2 ];
            if ( toks[ 3 ][ 0 ] == '\0' ) {
                ip->ti_attrname = NULL;
                NSLDAPI_FREE( toks[ 3 ] );
            } else {
                ip->ti_attrname = toks[ 3 ];
            }
            if ( toks[ 4 ] != NULL ) {  /* extra args. */
                for ( i = 0; toks[ i + 4 ] != NULL; ++i ) {
                    ;
                }
                if (( ip->ti_args = (char **)NSLDAPI_CALLOC( i + 1,
                        sizeof( char * ))) == NULL ) {
                    free_disptmpl( tmpl );
                    return( LDAP_TMPL_ERR_MEM );
                }
                for ( i = 0; toks[ i + 4 ] != NULL; ++i ) {
                    ip->ti_args[ i ] = toks[ i + 4 ];
                }
            }
            NSLDAPI_FREE( (char *)toks );

            if ( tmpl->dt_items == NULL ) {
                tmpl->dt_items = rowp = ip;
            } else if ( samerow ) {
                previp->ti_next_in_row = ip;
            } else {
                rowp->ti_next_in_col = ip;
                rowp = ip;
            }
            previp = ip;
            samerow = 0;
        } else if ( strcasecmp( toks[ 0 ], "samerow" ) == 0 ) {
            ldap_free_strarray( toks );
            samerow = 1;
        } else {
            ldap_free_strarray( toks );
            free_disptmpl( tmpl );
            return( LDAP_TMPL_ERR_SYNTAX );
        }
    }
    if ( tokcnt < 0 ) {
        free_disptmpl( tmpl );
        return( LDAP_TMPL_ERR_SYNTAX );
    }

    *tmplp = tmpl;
    return( 0 );
}


struct tmplerror {
        int     e_code;
        char    *e_reason;
};

#ifdef SUN
static struct tmplerror ldap_tmplerrlist[] = {
        { LDAP_TMPL_ERR_VERSION, 0},
        { LDAP_TMPL_ERR_MEM,     0},
        { LDAP_TMPL_ERR_SYNTAX,  0},
        { LDAP_TMPL_ERR_FILE,    0},
        { -1, 0 }
};
#else
static struct tmplerror ldap_tmplerrlist[] = {
        { LDAP_TMPL_ERR_VERSION, "Bad template version"         },
        { LDAP_TMPL_ERR_MEM,     "Out of memory"                },
        { LDAP_TMPL_ERR_SYNTAX,  "Bad template syntax"          },
        { LDAP_TMPL_ERR_FILE,    "File error reading template"  },
        { -1, 0 }
};
#endif

char *
LDAP_CALL
ldap_tmplerr2string( int err )
{
        static int init_flag = 0;
        int     i;

        /* Multiple threads should be ok since they assign same strings */
        if (init_flag == 0) {
                ldap_tmplerrlist[0].e_reason =
                        dgettext(TEXT_DOMAIN, "Bad template version");
                ldap_tmplerrlist[1].e_reason =
                        dgettext(TEXT_DOMAIN, "Out of memory");
                ldap_tmplerrlist[2].e_reason =
                        dgettext(TEXT_DOMAIN, "Bad template syntax");
                ldap_tmplerrlist[3].e_reason =
                        dgettext(TEXT_DOMAIN, "File error reading template");
                init_flag = 1;
        }

        for ( i = 0; ldap_tmplerrlist[i].e_code != -1; i++ ) {
                if ( err == ldap_tmplerrlist[i].e_code )
                        return( ldap_tmplerrlist[i].e_reason );
        }

        return(dgettext(TEXT_DOMAIN, "Unknown error") );
}