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