]> git.sur5r.net Git - openldap/blob - libraries/libldap/disptmpl.c
Protoized, moved extern definitions to .h files, fixed related bugs.
[openldap] / libraries / libldap / disptmpl.c
1 /*
2  * Copyright (c) 1993, 1994 Regents of the University of Michigan.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms are permitted
6  * provided that this notice is preserved and that due credit is given
7  * to the University of Michigan at Ann Arbor. The name of the University
8  * may not be used to endorse or promote products derived from this
9  * software without specific prior written permission. This software
10  * is provided ``as is'' without express or implied warranty.
11  *
12  * disptmpl.c:  display template library routines for LDAP clients
13  * 7 March 1994 by Mark C Smith
14  */
15
16 #include "portable.h"
17
18 #include <stdio.h>
19 #include <stdlib.h>
20
21 #include <ac/ctype.h>
22 #include <ac/string.h>
23 #include <ac/time.h>
24 #include <ac/unistd.h>
25 extern char *strdup (const char *);
26
27 #ifdef HAVE_SYS_FILE_H
28 #include <sys/file.h>
29 #endif
30
31 #include "ldap-int.h"
32 #include "disptmpl.h"
33
34 static void free_disptmpl LDAP_P(( struct ldap_disptmpl *tmpl ));
35 static int read_next_tmpl LDAP_P(( char **bufp, long *blenp,
36         struct ldap_disptmpl **tmplp, int dtversion ));
37
38 static char             *tmploptions[] = {
39     "addable", "modrdn",
40     "altview",
41     NULL
42 };
43
44
45 static unsigned long    tmploptvals[] = {
46     LDAP_DTMPL_OPT_ADDABLE, LDAP_DTMPL_OPT_ALLOWMODRDN,
47     LDAP_DTMPL_OPT_ALTVIEW,
48 };
49
50
51 static char             *itemtypes[] = {
52     "cis",                      "mls",                  "dn",
53     "bool",                     "jpeg",                 "jpegbtn",
54     "fax",                      "faxbtn",               "audiobtn",
55     "time",                     "date",                 "url",
56     "searchact",                "linkact",              "adddnact",
57     "addact",                   "verifyact",            "mail",
58     NULL
59 };
60
61 static unsigned long    itemsynids[] = {
62     LDAP_SYN_CASEIGNORESTR,     LDAP_SYN_MULTILINESTR,  LDAP_SYN_DN,
63     LDAP_SYN_BOOLEAN,           LDAP_SYN_JPEGIMAGE,     LDAP_SYN_JPEGBUTTON,
64     LDAP_SYN_FAXIMAGE,          LDAP_SYN_FAXBUTTON,     LDAP_SYN_AUDIOBUTTON,
65     LDAP_SYN_TIME,              LDAP_SYN_DATE,          LDAP_SYN_LABELEDURL,
66     LDAP_SYN_SEARCHACTION,      LDAP_SYN_LINKACTION,    LDAP_SYN_ADDDNACTION,
67     LDAP_SYN_ADDDNACTION,       LDAP_SYN_VERIFYDNACTION,LDAP_SYN_RFC822ADDR,
68 };
69
70
71 static char             *itemoptions[] = {
72     "ro",                               "sort",
73     "1val",                             "hide",
74     "required",                         "hideiffalse",
75     NULL
76 };
77
78
79 static unsigned long    itemoptvals[] = {
80     LDAP_DITEM_OPT_READONLY,            LDAP_DITEM_OPT_SORTVALUES,
81     LDAP_DITEM_OPT_SINGLEVALUED,        LDAP_DITEM_OPT_HIDEIFEMPTY,
82     LDAP_DITEM_OPT_VALUEREQUIRED,       LDAP_DITEM_OPT_HIDEIFFALSE,
83 };
84
85
86 #define ADDEF_CONSTANT  "constant"
87 #define ADDEF_ADDERSDN  "addersdn"
88
89
90 int
91 ldap_init_templates( char *file, struct ldap_disptmpl **tmpllistp )
92 {
93     FILE        *fp;
94     char        *buf;
95     long        rlen, len;
96     int         rc, eof;
97
98     *tmpllistp = NULLDISPTMPL;
99
100     if (( fp = fopen( file, "r" )) == NULL ) {
101         return( LDAP_TMPL_ERR_FILE );
102     }
103
104     if ( fseek( fp, 0L, SEEK_END ) != 0 ) {     /* move to end to get len */
105         fclose( fp );
106         return( LDAP_TMPL_ERR_FILE );
107     }
108
109     len = ftell( fp );
110
111     if ( fseek( fp, 0L, SEEK_SET ) != 0 ) {     /* back to start of file */
112         fclose( fp );
113         return( LDAP_TMPL_ERR_FILE );
114     }
115
116     if (( buf = malloc( (size_t)len )) == NULL ) {
117         fclose( fp );
118         return( LDAP_TMPL_ERR_MEM );
119     }
120
121     rlen = fread( buf, 1, (size_t)len, fp );
122     eof = feof( fp );
123     fclose( fp );
124
125     if ( rlen != len && !eof ) {        /* error:  didn't get the whole file */
126         free( buf );
127         return( LDAP_TMPL_ERR_FILE );
128     }
129
130     rc = ldap_init_templates_buf( buf, rlen, tmpllistp );
131     free( buf );
132
133     return( rc );
134 }
135
136
137 int
138 ldap_init_templates_buf( char *buf, long buflen,
139         struct ldap_disptmpl **tmpllistp )
140 {
141     int                         rc=-1, version;
142     char                        **toks;
143     struct ldap_disptmpl        *prevtmpl, *tmpl;
144
145     *tmpllistp = prevtmpl = NULLDISPTMPL;
146
147     if ( next_line_tokens( &buf, &buflen, &toks ) != 2 ||
148             strcasecmp( toks[ 0 ], "version" ) != 0 ) {
149         free_strarray( toks );
150         return( LDAP_TMPL_ERR_SYNTAX );
151     }
152     version = atoi( toks[ 1 ] );
153     free_strarray( toks );
154     if ( version != LDAP_TEMPLATE_VERSION ) {
155         return( LDAP_TMPL_ERR_VERSION );
156     }
157
158     while ( buflen > 0 && ( rc = read_next_tmpl( &buf, &buflen, &tmpl,
159             version )) == 0 && tmpl != NULLDISPTMPL ) {
160         if ( prevtmpl == NULLDISPTMPL ) {
161             *tmpllistp = tmpl;
162         } else {
163             prevtmpl->dt_next = tmpl;
164         }
165         prevtmpl = tmpl;
166     }
167
168     if ( rc != 0 ) {
169         ldap_free_templates( *tmpllistp );
170     }
171
172     return( rc );
173 }
174             
175
176
177 void
178 ldap_free_templates( struct ldap_disptmpl *tmpllist )
179 {
180     struct ldap_disptmpl        *tp, *nexttp;
181
182     if ( tmpllist != NULL ) {
183         for ( tp = tmpllist; tp != NULL; tp = nexttp ) {
184             nexttp = tp->dt_next;
185             free_disptmpl( tp );
186         }
187     }
188 }
189
190
191 static void
192 free_disptmpl( struct ldap_disptmpl *tmpl )
193 {
194     if ( tmpl != NULL ) {
195         if ( tmpl->dt_name != NULL ) {
196             free(  tmpl->dt_name );
197         }
198
199         if ( tmpl->dt_pluralname != NULL ) {
200             free( tmpl->dt_pluralname );
201         }
202
203         if ( tmpl->dt_iconname != NULL ) {
204             free( tmpl->dt_iconname );
205         }
206
207         if ( tmpl->dt_authattrname != NULL ) {
208             free( tmpl->dt_authattrname );
209         }
210
211         if ( tmpl->dt_defrdnattrname != NULL ) {
212             free( tmpl->dt_defrdnattrname );
213         }
214
215         if ( tmpl->dt_defaddlocation != NULL ) {
216             free( tmpl->dt_defaddlocation );
217         }
218
219         if (  tmpl->dt_oclist != NULL ) {
220             struct ldap_oclist  *ocp, *nextocp;
221
222             for ( ocp = tmpl->dt_oclist; ocp != NULL; ocp = nextocp ) {
223                 nextocp = ocp->oc_next;
224                 free_strarray( ocp->oc_objclasses );
225                 free( ocp );
226             }
227         }
228
229         if (  tmpl->dt_adddeflist != NULL ) {
230             struct ldap_adddeflist      *adp, *nextadp;
231
232             for ( adp = tmpl->dt_adddeflist; adp != NULL; adp = nextadp ) {
233                 nextadp = adp->ad_next;
234                 if( adp->ad_attrname != NULL ) {
235                     free( adp->ad_attrname );
236                 }
237                 if( adp->ad_value != NULL ) {
238                     free( adp->ad_value );
239                 }
240                 free( adp );
241             }
242         }
243
244         if (  tmpl->dt_items != NULL ) {
245             struct ldap_tmplitem        *rowp, *nextrowp, *colp, *nextcolp;
246
247             for ( rowp = tmpl->dt_items; rowp != NULL; rowp = nextrowp ) {
248                 nextrowp = rowp->ti_next_in_col;
249                 for ( colp = rowp; colp != NULL; colp = nextcolp ) {
250                     nextcolp = colp->ti_next_in_row;
251                     if ( colp->ti_attrname != NULL ) {
252                         free( colp->ti_attrname );
253                     }
254                     if ( colp->ti_label != NULL ) {
255                         free( colp->ti_label );
256                     }
257                     if ( colp->ti_args != NULL ) {
258                         free_strarray( colp->ti_args );
259                     }
260                     free( colp );
261                 }
262             }
263         }
264
265         free( tmpl );
266     }
267 }
268
269
270 struct ldap_disptmpl *
271 ldap_first_disptmpl( struct ldap_disptmpl *tmpllist )
272 {
273     return( tmpllist );
274 }
275
276
277 struct ldap_disptmpl *
278 ldap_next_disptmpl( struct ldap_disptmpl *tmpllist,
279         struct ldap_disptmpl *tmpl )
280 {
281     return( tmpl == NULLDISPTMPL ? tmpl : tmpl->dt_next );
282 }
283
284
285 struct ldap_disptmpl *
286 ldap_name2template( char *name, struct ldap_disptmpl *tmpllist )
287 {
288     struct ldap_disptmpl        *dtp;
289
290     for ( dtp = ldap_first_disptmpl( tmpllist ); dtp != NULLDISPTMPL;
291             dtp = ldap_next_disptmpl( tmpllist, dtp )) {
292         if ( strcasecmp( name, dtp->dt_name ) == 0 ) {
293             return( dtp );
294         }
295     }
296
297     return( NULLDISPTMPL );
298 }
299
300
301 struct ldap_disptmpl *
302 ldap_oc2template( char **oclist, struct ldap_disptmpl *tmpllist )
303 {
304     struct ldap_disptmpl        *dtp;
305     struct ldap_oclist          *oclp;
306     int                         i, j, needcnt, matchcnt;
307
308     if ( tmpllist == NULL || oclist == NULL || oclist[ 0 ] == NULL ) {
309         return( NULLDISPTMPL );
310     }
311
312     for ( dtp = ldap_first_disptmpl( tmpllist ); dtp != NULLDISPTMPL;
313                 dtp = ldap_next_disptmpl( tmpllist, dtp )) {
314         for ( oclp = dtp->dt_oclist; oclp != NULLOCLIST;
315                 oclp = oclp->oc_next ) {
316             needcnt = matchcnt = 0;
317             for ( i = 0; oclp->oc_objclasses[ i ] != NULL; ++i ) {
318                 for ( j = 0; oclist[ j ] != NULL; ++j ) {
319                     if ( strcasecmp( oclist[ j ], oclp->oc_objclasses[ i ] )
320                             == 0 ) {
321                         ++matchcnt;
322                     }
323                 }
324                 ++needcnt;
325             }
326
327             if ( matchcnt == needcnt ) {
328                 return( dtp );
329             }
330         }
331     }
332
333     return( NULLDISPTMPL );
334 }
335
336
337 struct ldap_tmplitem *
338 ldap_first_tmplrow( struct ldap_disptmpl *tmpl )
339 {
340     return( tmpl->dt_items );
341 }
342
343
344 struct ldap_tmplitem *
345 ldap_next_tmplrow( struct ldap_disptmpl *tmpl, struct ldap_tmplitem *row )
346 {
347     return( row == NULLTMPLITEM ? row : row->ti_next_in_col );
348 }
349
350
351 struct ldap_tmplitem *
352 ldap_first_tmplcol( struct ldap_disptmpl *tmpl, struct ldap_tmplitem *row )
353 {
354     return( row );
355 }
356
357
358 struct ldap_tmplitem *
359 ldap_next_tmplcol( struct ldap_disptmpl *tmpl, struct ldap_tmplitem *row,
360         struct ldap_tmplitem *col )
361 {
362     return( col == NULLTMPLITEM ? col : col->ti_next_in_row );
363 }
364
365
366 char **
367 ldap_tmplattrs( struct ldap_disptmpl *tmpl, char **includeattrs,
368         int exclude, unsigned long syntaxmask )
369 {
370 /*
371  * this routine should filter out duplicate attributes...
372  */
373     struct ldap_tmplitem        *tirowp, *ticolp;
374     int                 i, attrcnt, memerr;
375     char                **attrs;
376
377     attrcnt = 0;
378     memerr = 0;
379
380     if (( attrs = (char **)malloc( sizeof( char * ))) == NULL ) {
381         return( NULL );
382     }
383
384     if ( includeattrs != NULL ) {
385         for ( i = 0; !memerr && includeattrs[ i ] != NULL; ++i ) {
386             if (( attrs = (char **)realloc( attrs, ( attrcnt + 2 ) *
387                     sizeof( char * ))) == NULL || ( attrs[ attrcnt++ ] =
388                     strdup( includeattrs[ i ] )) == NULL ) {
389                 memerr = 1;
390             } else {
391                 attrs[ attrcnt ] = NULL;
392             }
393         }
394     }
395
396     for ( tirowp = ldap_first_tmplrow( tmpl );
397             !memerr && tirowp != NULLTMPLITEM;
398             tirowp = ldap_next_tmplrow( tmpl, tirowp )) {
399         for ( ticolp = ldap_first_tmplcol( tmpl, tirowp );
400                 ticolp != NULLTMPLITEM;
401                 ticolp = ldap_next_tmplcol( tmpl, tirowp, ticolp )) {
402
403             if ( syntaxmask != 0 ) {
404                 if (( exclude &&
405                         ( syntaxmask & ticolp->ti_syntaxid ) != 0 ) ||
406                         ( !exclude &&
407                         ( syntaxmask & ticolp->ti_syntaxid ) == 0 )) {
408                     continue;
409                 }
410             }
411
412             if ( ticolp->ti_attrname != NULL ) {
413                 if (( attrs = (char **)realloc( attrs, ( attrcnt + 2 ) *
414                         sizeof( char * ))) == NULL || ( attrs[ attrcnt++ ] =
415                         strdup( ticolp->ti_attrname )) == NULL ) {
416                     memerr = 1;
417                 } else {
418                     attrs[ attrcnt ] = NULL;
419                 }
420             }
421         }
422     }
423
424     if ( memerr || attrcnt == 0 ) {
425         for ( i = 0; i < attrcnt; ++i ) {
426             if ( attrs[ i ] != NULL ) {
427                 free( attrs[ i ] );
428             }
429         }
430
431         free( (char *)attrs );
432         return( NULL );
433     }
434
435     return( attrs );
436 }
437
438
439 static int
440 read_next_tmpl( char **bufp, long *blenp, struct ldap_disptmpl **tmplp,
441         int dtversion )
442 {
443     int                         i, j, tokcnt, samerow, adsource;
444     char                        **toks, *itemopts;
445     struct ldap_disptmpl        *tmpl;
446     struct ldap_oclist          *ocp, *prevocp = NULL;
447     struct ldap_adddeflist      *adp, *prevadp = NULL;
448     struct ldap_tmplitem        *rowp = NULL, *ip, *previp = NULL;
449
450     *tmplp = NULL;
451
452     /*
453      * template name comes first
454      */
455     if (( tokcnt = next_line_tokens( bufp, blenp, &toks )) != 1 ) {
456         free_strarray( toks );
457         return( tokcnt == 0 ? 0 : LDAP_TMPL_ERR_SYNTAX );
458     }
459
460     if (( tmpl = (struct ldap_disptmpl *)calloc( 1,
461             sizeof( struct ldap_disptmpl ))) == NULL ) {
462         free_strarray( toks );
463         return(  LDAP_TMPL_ERR_MEM );
464     }
465     tmpl->dt_name = toks[ 0 ];
466     free( (char *)toks );
467
468     /*
469      * template plural name comes next
470      */
471     if (( tokcnt = next_line_tokens( bufp, blenp, &toks )) != 1 ) {
472         free_strarray( toks );
473         free_disptmpl( tmpl );
474         return( LDAP_TMPL_ERR_SYNTAX );
475     }
476     tmpl->dt_pluralname = toks[ 0 ];
477     free( (char *)toks );
478
479     /*
480      * template icon name is next
481      */
482     if (( tokcnt = next_line_tokens( bufp, blenp, &toks )) != 1 ) {
483         free_strarray( toks );
484         free_disptmpl( tmpl );
485         return( LDAP_TMPL_ERR_SYNTAX );
486     }
487     tmpl->dt_iconname = toks[ 0 ];
488     free( (char *)toks );
489
490     /*
491      * template options come next
492      */
493     if (( tokcnt = next_line_tokens( bufp, blenp, &toks )) < 1 ) {
494         free_strarray( toks );
495         free_disptmpl( tmpl );
496         return( LDAP_TMPL_ERR_SYNTAX );
497     }
498     for ( i = 0; toks[ i ] != NULL; ++i ) {
499         for ( j = 0; tmploptions[ j ] != NULL; ++j ) {
500             if ( strcasecmp( toks[ i ], tmploptions[ j ] ) == 0 ) {
501                 tmpl->dt_options |= tmploptvals[ j ];
502             }
503         }
504     }
505     free_strarray( toks );
506
507     /*
508      * object class list is next
509      */
510     while (( tokcnt = next_line_tokens( bufp, blenp, &toks )) > 0 ) {
511         if (( ocp = (struct ldap_oclist *)calloc( 1,
512                 sizeof( struct ldap_oclist ))) == NULL ) {
513             free_strarray( toks );
514             free_disptmpl( tmpl );
515             return( LDAP_TMPL_ERR_MEM );
516         }
517         ocp->oc_objclasses = toks;
518         if ( tmpl->dt_oclist == NULL ) {
519             tmpl->dt_oclist = ocp;
520         } else {
521             prevocp->oc_next = ocp;
522         }
523         prevocp = ocp;
524     }
525     if ( tokcnt < 0 ) {
526         free_disptmpl( tmpl );
527         return( LDAP_TMPL_ERR_SYNTAX );
528     }
529
530     /*
531      * read name of attribute to authenticate as
532      */
533     if (( tokcnt = next_line_tokens( bufp, blenp, &toks )) != 1 ) {
534         free_strarray( toks );
535         free_disptmpl( tmpl );
536         return( LDAP_TMPL_ERR_SYNTAX );
537     }
538     if ( toks[ 0 ][ 0 ] != '\0' ) {
539         tmpl->dt_authattrname = toks[ 0 ];
540     } else {
541         free( toks[ 0 ] );
542     }
543     free( (char *)toks );
544
545     /*
546      * read default attribute to use for RDN
547      */
548     if (( tokcnt = next_line_tokens( bufp, blenp, &toks )) != 1 ) {
549         free_strarray( toks );
550         free_disptmpl( tmpl );
551         return( LDAP_TMPL_ERR_SYNTAX );
552     }
553     tmpl->dt_defrdnattrname = toks[ 0 ];
554     free( (char *)toks );
555
556     /*
557      * read default location for new entries
558      */
559     if (( tokcnt = next_line_tokens( bufp, blenp, &toks )) != 1 ) {
560         free_strarray( toks );
561         free_disptmpl( tmpl );
562         return( LDAP_TMPL_ERR_SYNTAX );
563     }
564     if ( toks[ 0 ][ 0 ] != '\0' ) {
565         tmpl->dt_defaddlocation = toks[ 0 ];
566     } else {
567         free( toks[ 0 ] );
568     }
569     free( (char *)toks );
570
571     /*
572      * read list of rules used to define default values for new entries
573      */
574     while (( tokcnt = next_line_tokens( bufp, blenp, &toks )) > 0 ) {
575         if ( strcasecmp( ADDEF_CONSTANT, toks[ 0 ] ) == 0 ) {
576             adsource = LDAP_ADSRC_CONSTANTVALUE;
577         } else if ( strcasecmp( ADDEF_ADDERSDN, toks[ 0 ] ) == 0 ) {
578             adsource = LDAP_ADSRC_ADDERSDN;
579         } else {
580             adsource = 0;
581         }
582         if ( adsource == 0 || tokcnt < 2 ||
583                 ( adsource == LDAP_ADSRC_CONSTANTVALUE && tokcnt != 3 ) ||
584                 ( adsource == LDAP_ADSRC_ADDERSDN && tokcnt != 2 )) {
585             free_strarray( toks );
586             free_disptmpl( tmpl );
587             return( LDAP_TMPL_ERR_SYNTAX );
588         }
589                 
590         if (( adp = (struct ldap_adddeflist *)calloc( 1,
591                 sizeof( struct ldap_adddeflist ))) == NULL ) {
592             free_strarray( toks );
593             free_disptmpl( tmpl );
594             return( LDAP_TMPL_ERR_MEM );
595         }
596         adp->ad_source = adsource;
597         adp->ad_attrname = toks[ 1 ];
598         if ( adsource == LDAP_ADSRC_CONSTANTVALUE ) {
599             adp->ad_value = toks[ 2 ];
600         }
601         free( toks[ 0 ] );
602         free( (char *)toks );
603
604         if ( tmpl->dt_adddeflist == NULL ) {
605             tmpl->dt_adddeflist = adp;
606         } else {
607             prevadp->ad_next = adp;
608         }
609         prevadp = adp;
610     }
611
612     /*
613      * item list is next
614      */
615     samerow = 0;
616     while (( tokcnt = next_line_tokens( bufp, blenp, &toks )) > 0 ) {
617         if ( strcasecmp( toks[ 0 ], "item" ) == 0 ) {
618             if ( tokcnt < 4 ) {
619                 free_strarray( toks );
620                 free_disptmpl( tmpl );
621                 return( LDAP_TMPL_ERR_SYNTAX );
622             }
623
624             if (( ip = (struct ldap_tmplitem *)calloc( 1,
625                     sizeof( struct ldap_tmplitem ))) == NULL ) {
626                 free_strarray( toks );
627                 free_disptmpl( tmpl );
628                 return( LDAP_TMPL_ERR_MEM );
629             }
630
631             /*
632              * find syntaxid from config file string
633              */
634             while (( itemopts = strrchr( toks[ 1 ], ',' )) != NULL ) {
635                 *itemopts++ = '\0';
636                 for ( i = 0; itemoptions[ i ] != NULL; ++i ) {
637                     if ( strcasecmp( itemopts, itemoptions[ i ] ) == 0 ) {
638                         break;
639                     }
640                 }
641                 if ( itemoptions[ i ] == NULL ) {
642                     free_strarray( toks );
643                     free_disptmpl( tmpl );
644                     return( LDAP_TMPL_ERR_SYNTAX );
645                 }
646                 ip->ti_options |= itemoptvals[ i ];
647             }
648
649             for ( i = 0; itemtypes[ i ] != NULL; ++i ) {
650                 if ( strcasecmp( toks[ 1 ], itemtypes[ i ] ) == 0 ) {
651                     break;
652                 }
653             }
654             if ( itemtypes[ i ] == NULL ) {
655                 free_strarray( toks );
656                 free_disptmpl( tmpl );
657                 return( LDAP_TMPL_ERR_SYNTAX );
658             }
659
660             free( toks[ 0 ] );
661             free( toks[ 1 ] );
662             ip->ti_syntaxid = itemsynids[ i ];
663             ip->ti_label = toks[ 2 ];
664             if ( toks[ 3 ][ 0 ] == '\0' ) {
665                 ip->ti_attrname = NULL;
666                 free( toks[ 3 ] );
667             } else {
668                 ip->ti_attrname = toks[ 3 ];
669             }
670             if ( toks[ 4 ] != NULL ) {  /* extra args. */
671                 for ( i = 0; toks[ i + 4 ] != NULL; ++i ) {
672                     ;
673                 }
674                 if (( ip->ti_args = (char **) calloc( i + 1, sizeof( char * )))
675                         == NULL ) {
676                     free_disptmpl( tmpl );
677                     return( LDAP_TMPL_ERR_MEM );
678                 }
679                 for ( i = 0; toks[ i + 4 ] != NULL; ++i ) {
680                     ip->ti_args[ i ] = toks[ i + 4 ];
681                 }
682             }
683             free( (char *)toks );
684
685             if ( tmpl->dt_items == NULL ) {
686                 tmpl->dt_items = rowp = ip;
687             } else if ( samerow ) {
688                 previp->ti_next_in_row = ip;
689             } else {
690                 rowp->ti_next_in_col = ip;
691                 rowp = ip;
692             }
693             previp = ip;
694             samerow = 0;
695         } else if ( strcasecmp( toks[ 0 ], "samerow" ) == 0 ) {
696             free_strarray( toks );
697             samerow = 1;
698         } else {
699             free_strarray( toks );
700             free_disptmpl( tmpl );
701             return( LDAP_TMPL_ERR_SYNTAX );
702         }
703     }
704     if ( tokcnt < 0 ) {
705         free_disptmpl( tmpl );
706         return( LDAP_TMPL_ERR_SYNTAX );
707     }
708
709     *tmplp = tmpl;
710     return( 0 );
711 }