1/* dsaschema.c */
2/* $OpenLDAP$ */
3/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
4 *
5 * Copyright 2004-2011 The OpenLDAP Foundation.
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted only as authorized by the OpenLDAP
10 * Public License.
11 *
12 * A copy of this license is available in the file LICENSE in the
13 * top-level directory of the distribution or, alternatively, at
14 * <http://www.OpenLDAP.org/license.html>.
15 */
16
17#include <portable.h>
18
19#include <ac/string.h>
20#include <ac/ctype.h>
21#include <ac/signal.h>
22#include <ac/errno.h>
23#include <ac/stdlib.h>
24#include <ac/ctype.h>
25#include <ac/time.h>
26#include <ac/unistd.h>
27
28#include <stdio.h>
29
30/*
31 * Schema reader that allows us to define DSA schema (including
32 * operational attributes and non-user object classes)
33 *
34 * A kludge, at best, and in order to avoid including slapd
35 * headers we use fprintf() rather than slapd's native logging,
36 * which may confuse users...
37 *
38 */
39
40#include <ldap.h>
41#include <ldap_schema.h>
42
43extern int at_add(LDAPAttributeType *at, const char **err);
44extern int oc_add(LDAPObjectClass *oc, int user, const char **err);
45extern int cr_add(LDAPContentRule *cr, int user, const char **err);
46
47#define ARGS_STEP 512
48
49static char *fp_getline(FILE *fp, int *lineno);
50static void fp_getline_init(int *lineno);
51static int fp_parse_line(int lineno, char *line);
52static char *strtok_quote( char *line, char *sep );
53
54static char **cargv = NULL;
55static int cargv_size = 0;
56static int cargc = 0;
57static char *strtok_quote_ptr;
58
59int init_module(int argc, char *argv[]);
60
61static int dsaschema_parse_at(const char *fname, int lineno, char *line, char **argv)
62{
63	LDAPAttributeType *at;
64	int code;
65	const char *err;
66
67	at = ldap_str2attributetype(line, &code, &err, LDAP_SCHEMA_ALLOW_ALL);
68	if (!at) {
69		fprintf(stderr, "%s: line %d: %s before %s\n",
70			fname, lineno, ldap_scherr2str(code), err);
71		return 1;
72	}
73
74	if (at->at_oid == NULL) {
75		fprintf(stderr, "%s: line %d: attributeType has no OID\n",
76			fname, lineno);
77		return 1;
78	}
79
80	code = at_add(at, &err);
81	if (code) {
82		fprintf(stderr, "%s: line %d: %s: \"%s\"\n",
83			fname, lineno, ldap_scherr2str(code), err);
84		return 1;
85	}
86
87	ldap_memfree(at);
88
89	return 0;
90}
91
92static int dsaschema_parse_oc(const char *fname, int lineno, char *line, char **argv)
93{
94	LDAPObjectClass *oc;
95	int code;
96	const char *err;
97
98	oc = ldap_str2objectclass(line, &code, &err, LDAP_SCHEMA_ALLOW_ALL);
99	if (!oc) {
100		fprintf(stderr, "%s: line %d: %s before %s\n",
101			fname, lineno, ldap_scherr2str(code), err);
102		return 1;
103	}
104
105	if (oc->oc_oid == NULL) {
106		fprintf(stderr,
107			"%s: line %d: objectclass has no OID\n",
108			fname, lineno);
109		return 1;
110	}
111
112	code = oc_add(oc, 0, &err);
113	if (code) {
114		fprintf(stderr, "%s: line %d: %s: \"%s\"\n",
115			fname, lineno, ldap_scherr2str(code), err);
116		return 1;
117	}
118
119	ldap_memfree(oc);
120	return 0;
121}
122
123static int dsaschema_parse_cr(const char *fname, int lineno, char *line, char **argv)
124{
125	LDAPContentRule *cr;
126	int code;
127	const char *err;
128
129	cr = ldap_str2contentrule(line, &code, &err, LDAP_SCHEMA_ALLOW_ALL);
130	if (!cr) {
131		fprintf(stderr, "%s: line %d: %s before %s\n",
132			fname, lineno, ldap_scherr2str(code), err);
133		return 1;
134	}
135
136	if (cr->cr_oid == NULL) {
137		fprintf(stderr,
138			"%s: line %d: objectclass has no OID\n",
139			fname, lineno);
140		return 1;
141	}
142
143	code = cr_add(cr, 0, &err);
144	if (code) {
145		fprintf(stderr, "%s: line %d: %s: \"%s\"\n",
146			fname, lineno, ldap_scherr2str(code), err);
147		return 1;
148	}
149
150	ldap_memfree(cr);
151	return 0;
152}
153
154static int dsaschema_read_config(const char *fname, int depth)
155{
156	FILE *fp;
157	char *line, *savefname, *saveline;
158	int savelineno, lineno;
159	int rc;
160
161	if (depth == 0) {
162		cargv = calloc(ARGS_STEP + 1, sizeof(*cargv));
163		if (cargv == NULL) {
164			return 1;
165		}
166		cargv_size = ARGS_STEP + 1;
167	}
168
169	fp = fopen(fname, "r");
170	if (fp == NULL) {
171		fprintf(stderr, "could not open config file \"%s\": %s (%d)\n",
172			fname, strerror(errno), errno);
173		return 1;
174	}
175	fp_getline_init(&lineno);
176
177	while ((line = fp_getline(fp, &lineno)) != NULL) {
178		/* skip comments and blank lines */
179		if (line[0] == '#' || line[0] == '\0') {
180			continue;
181		}
182
183		saveline = strdup(line);
184		if (saveline == NULL) {
185			return 1;
186		}
187
188		if (fp_parse_line(lineno, line) != 0) {
189			return 1;
190		}
191
192		if (cargc < 1) {
193			continue;
194		}
195
196		if (strcasecmp(cargv[0], "attributetype") == 0 ||
197		    strcasecmp(cargv[0], "attribute") == 0) {
198			if (cargc < 2) {
199				fprintf(stderr, "%s: line %d: illegal attribute type format\n",
200					fname, lineno);
201				return 1;
202			} else if (*cargv[1] == '(' /*')'*/) {
203				char *p;
204
205				p = strchr(saveline, '(' /*')'*/);
206				rc = dsaschema_parse_at(fname, lineno, p, cargv);
207				if (rc != 0)
208					return rc;
209			} else {
210				fprintf(stderr, "%s: line %d: old attribute type format not supported\n",
211					fname, lineno);
212			}
213		} else if (strcasecmp(cargv[0], "ditcontentrule") == 0) {
214			char *p;
215			p = strchr(saveline, '(' /*')'*/);
216			rc = dsaschema_parse_cr(fname, lineno, p, cargv);
217			if (rc != 0)
218				return rc;
219		} else if (strcasecmp(cargv[0], "objectclass") == 0) {
220			if (cargc < 2) {
221				fprintf(stderr, "%s: line %d: illegal objectclass format\n",
222					fname, lineno);
223				return 1;
224			} else if (*cargv[1] == '(' /*')'*/) {
225				char *p;
226
227				p = strchr(saveline, '(' /*')'*/);
228				rc = dsaschema_parse_oc(fname, lineno, p, cargv);
229				if (rc != 0)
230					return rc;
231			} else {
232				fprintf(stderr, "%s: line %d: object class format not supported\n",
233					fname, lineno);
234			}
235		} else if (strcasecmp(cargv[0], "include") == 0) {
236			if (cargc < 2) {
237				fprintf(stderr, "%s: line %d: missing file name in \"include <filename>\" line",
238					fname, lineno);
239				return 1;
240			}
241			savefname = strdup(cargv[1]);
242			if (savefname == NULL) {
243				return 1;
244			}
245			if (dsaschema_read_config(savefname, depth + 1) != 0) {
246				return 1;
247			}
248			free(savefname);
249			lineno = savelineno - 1;
250		} else {
251			fprintf(stderr, "%s: line %d: unknown directive \"%s\" (ignored)\n",
252				fname, lineno, cargv[0]);
253		}
254	}
255
256	fclose(fp);
257
258	if (depth == 0)
259		free(cargv);
260
261	return 0;
262}
263
264int init_module(int argc, char *argv[])
265{
266	int i;
267	int rc;
268
269	for (i = 0; i < argc; i++) {
270		rc = dsaschema_read_config(argv[i], 0);
271		if (rc != 0) {
272			break;
273		}
274	}
275
276	return rc;
277}
278
279
280static int
281fp_parse_line(
282    int		lineno,
283    char	*line
284)
285{
286	char *	token;
287
288	cargc = 0;
289	token = strtok_quote( line, " \t" );
290
291	if ( strtok_quote_ptr ) {
292		*strtok_quote_ptr = ' ';
293	}
294
295	if ( strtok_quote_ptr ) {
296		*strtok_quote_ptr = '\0';
297	}
298
299	for ( ; token != NULL; token = strtok_quote( NULL, " \t" ) ) {
300		if ( cargc == cargv_size - 1 ) {
301			char **tmp;
302			tmp = realloc( cargv, (cargv_size + ARGS_STEP) *
303					    sizeof(*cargv) );
304			if ( tmp == NULL ) {
305				return -1;
306			}
307			cargv = tmp;
308			cargv_size += ARGS_STEP;
309		}
310		cargv[cargc++] = token;
311	}
312	cargv[cargc] = NULL;
313	return 0;
314}
315
316static char *
317strtok_quote( char *line, char *sep )
318{
319	int		inquote;
320	char		*tmp;
321	static char	*next;
322
323	strtok_quote_ptr = NULL;
324	if ( line != NULL ) {
325		next = line;
326	}
327	while ( *next && strchr( sep, *next ) ) {
328		next++;
329	}
330
331	if ( *next == '\0' ) {
332		next = NULL;
333		return( NULL );
334	}
335	tmp = next;
336
337	for ( inquote = 0; *next; ) {
338		switch ( *next ) {
339		case '"':
340			if ( inquote ) {
341				inquote = 0;
342			} else {
343				inquote = 1;
344			}
345			AC_MEMCPY( next, next + 1, strlen( next + 1 ) + 1 );
346			break;
347
348		case '\\':
349			if ( next[1] )
350				AC_MEMCPY( next,
351					    next + 1, strlen( next + 1 ) + 1 );
352			next++;		/* dont parse the escaped character */
353			break;
354
355		default:
356			if ( ! inquote ) {
357				if ( strchr( sep, *next ) != NULL ) {
358					strtok_quote_ptr = next;
359					*next++ = '\0';
360					return( tmp );
361				}
362			}
363			next++;
364			break;
365		}
366	}
367
368	return( tmp );
369}
370
371static char	buf[BUFSIZ];
372static char	*line;
373static size_t lmax, lcur;
374
375#define CATLINE( buf ) \
376	do { \
377		size_t len = strlen( buf ); \
378		while ( lcur + len + 1 > lmax ) { \
379			lmax += BUFSIZ; \
380			line = (char *) realloc( line, lmax ); \
381		} \
382		strcpy( line + lcur, buf ); \
383		lcur += len; \
384	} while( 0 )
385
386static char *
387fp_getline( FILE *fp, int *lineno )
388{
389	char		*p;
390
391	lcur = 0;
392	CATLINE( buf );
393	(*lineno)++;
394
395	/* hack attack - keeps us from having to keep a stack of bufs... */
396	if ( strncasecmp( line, "include", 7 ) == 0 ) {
397		buf[0] = '\0';
398		return( line );
399	}
400
401	while ( fgets( buf, sizeof(buf), fp ) != NULL ) {
402		/* trim off \r\n or \n */
403		if ( (p = strchr( buf, '\n' )) != NULL ) {
404			if( p > buf && p[-1] == '\r' ) --p;
405			*p = '\0';
406		}
407
408		/* trim off trailing \ and append the next line */
409		if ( line[ 0 ] != '\0'
410				&& (p = line + strlen( line ) - 1)[ 0 ] == '\\'
411				&& p[ -1 ] != '\\' ) {
412			p[ 0 ] = '\0';
413			lcur--;
414
415		} else {
416			if ( ! isspace( (unsigned char) buf[0] ) ) {
417				return( line );
418			}
419
420			/* change leading whitespace to a space */
421			buf[0] = ' ';
422		}
423
424		CATLINE( buf );
425		(*lineno)++;
426	}
427	buf[0] = '\0';
428
429	return( line[0] ? line : NULL );
430}
431
432static void
433fp_getline_init( int *lineno )
434{
435	*lineno = -1;
436	buf[0] = '\0';
437}
438
439