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