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