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