1/*
2   ldb database library
3
4   Copyright (C) Simo Sorce 2005
5
6     ** NOTE! The following LGPL license applies to the ldb
7     ** library. This does NOT imply that all of Samba is released
8     ** under the LGPL
9
10   This library is free software; you can redistribute it and/or
11   modify it under the terms of the GNU Lesser General Public
12   License as published by the Free Software Foundation; either
13   version 3 of the License, or (at your option) any later version.
14
15   This library is distributed in the hope that it will be useful,
16   but WITHOUT ANY WARRANTY; without even the implied warranty of
17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18   Lesser General Public License for more details.
19
20   You should have received a copy of the GNU Lesser General Public
21   License along with this library; if not, see <http://www.gnu.org/licenses/>.
22*/
23
24/*
25 *  Name: ldb
26 *
27 *  Component: oLschema2ldif
28 *
29 *  Description: utility to convert an OpenLDAP schema into AD LDIF
30 *
31 *  Author: Simo Sorce
32 */
33
34#include "includes.h"
35#include "ldb.h"
36#include "tools/cmdline.h"
37#include "dsdb/samdb/samdb.h"
38
39#define SCHEMA_UNKNOWN 0
40#define SCHEMA_NAME 1
41#define SCHEMA_SUP 2
42#define SCHEMA_STRUCTURAL 3
43#define SCHEMA_ABSTRACT 4
44#define SCHEMA_AUXILIARY 5
45#define SCHEMA_MUST 6
46#define SCHEMA_MAY 7
47#define SCHEMA_SINGLE_VALUE 8
48#define SCHEMA_EQUALITY 9
49#define SCHEMA_ORDERING 10
50#define SCHEMA_SUBSTR 11
51#define SCHEMA_SYNTAX 12
52#define SCHEMA_DESC 13
53
54struct schema_conv {
55	int count;
56	int failures;
57};
58
59struct schema_token {
60	int type;
61	char *value;
62};
63
64struct ldb_context *ldb_ctx;
65struct ldb_dn *basedn;
66
67static int check_braces(const char *string)
68{
69	int b;
70	char *c;
71
72	b = 0;
73	if ((c = strchr(string, '(')) == NULL) {
74		return -1;
75	}
76	b++;
77	c++;
78	while (b) {
79		c = strpbrk(c, "()");
80		if (c == NULL) return 1;
81		if (*c == '(') b++;
82		if (*c == ')') b--;
83		c++;
84	}
85	return 0;
86}
87
88static char *skip_spaces(char *string) {
89	return (string + strspn(string, " \t\n"));
90}
91
92static int add_multi_string(struct ldb_message *msg, const char *attr, char *values)
93{
94	char *c;
95	char *s;
96	int n;
97
98	c = skip_spaces(values);
99	while (*c) {
100		n = strcspn(c, " \t$");
101		s = talloc_strndup(msg, c, n);
102		if (ldb_msg_add_string(msg, attr, s) != 0) {
103			return -1;
104		}
105		c += n;
106		c += strspn(c, " \t$");
107	}
108
109	return 0;
110}
111
112#define MSG_ADD_STRING(a, v) do { if (ldb_msg_add_string(msg, a, v) != 0) goto failed; } while(0)
113#define MSG_ADD_M_STRING(a, v) do { if (add_multi_string(msg, a, v) != 0) goto failed; } while(0)
114
115static char *get_def_value(TALLOC_CTX *ctx, char **string)
116{
117	char *c = *string;
118	char *value;
119	int n;
120
121	if (*c == '\'') {
122		c++;
123		n = strcspn(c, "\'");
124		value = talloc_strndup(ctx, c, n);
125		c += n;
126		c++; /* skip closing \' */
127	} else {
128		n = strcspn(c, " \t\n");
129		value = talloc_strndup(ctx, c, n);
130		c += n;
131	}
132	*string = c;
133
134	return value;
135}
136
137static struct schema_token *get_next_schema_token(TALLOC_CTX *ctx, char **string)
138{
139	char *c = skip_spaces(*string);
140	char *type;
141	struct schema_token *token;
142	int n;
143
144	token = talloc(ctx, struct schema_token);
145
146	n = strcspn(c, " \t\n");
147	type = talloc_strndup(token, c, n);
148	c += n;
149	c = skip_spaces(c);
150
151	if (strcasecmp("NAME", type) == 0) {
152		talloc_free(type);
153		token->type = SCHEMA_NAME;
154		/* we do not support aliases so we get only the first name given and skip others */
155		if (*c == '(') {
156			char *s = strchr(c, ')');
157			if (s == NULL) return NULL;
158			s = skip_spaces(s);
159			*string = s;
160
161			c++;
162			c = skip_spaces(c);
163		}
164
165		token->value = get_def_value(ctx, &c);
166
167		if (*string < c) { /* single name */
168			c = skip_spaces(c);
169			*string = c;
170		}
171		return token;
172	}
173	if (strcasecmp("SUP", type) == 0) {
174		talloc_free(type);
175		token->type = SCHEMA_SUP;
176
177		if (*c == '(') {
178			c++;
179			n = strcspn(c, ")");
180			token->value = talloc_strndup(ctx, c, n);
181			c += n;
182			c++;
183		} else {
184			token->value = get_def_value(ctx, &c);
185		}
186
187		c = skip_spaces(c);
188		*string = c;
189		return token;
190	}
191
192	if (strcasecmp("STRUCTURAL", type) == 0) {
193		talloc_free(type);
194		token->type = SCHEMA_STRUCTURAL;
195		*string = c;
196		return token;
197	}
198
199	if (strcasecmp("ABSTRACT", type) == 0) {
200		talloc_free(type);
201		token->type = SCHEMA_ABSTRACT;
202		*string = c;
203		return token;
204	}
205
206	if (strcasecmp("AUXILIARY", type) == 0) {
207		talloc_free(type);
208		token->type = SCHEMA_AUXILIARY;
209		*string = c;
210		return token;
211	}
212
213	if (strcasecmp("MUST", type) == 0) {
214		talloc_free(type);
215		token->type = SCHEMA_MUST;
216
217		if (*c == '(') {
218			c++;
219			n = strcspn(c, ")");
220			token->value = talloc_strndup(ctx, c, n);
221			c += n;
222			c++;
223		} else {
224			token->value = get_def_value(ctx, &c);
225		}
226
227		c = skip_spaces(c);
228		*string = c;
229		return token;
230	}
231
232	if (strcasecmp("MAY", type) == 0) {
233		talloc_free(type);
234		token->type = SCHEMA_MAY;
235
236		if (*c == '(') {
237			c++;
238			n = strcspn(c, ")");
239			token->value = talloc_strndup(ctx, c, n);
240			c += n;
241			c++;
242		} else {
243			token->value = get_def_value(ctx, &c);
244		}
245
246		c = skip_spaces(c);
247		*string = c;
248		return token;
249	}
250
251	if (strcasecmp("SINGLE-VALUE", type) == 0) {
252		talloc_free(type);
253		token->type = SCHEMA_SINGLE_VALUE;
254		*string = c;
255		return token;
256	}
257
258	if (strcasecmp("EQUALITY", type) == 0) {
259		talloc_free(type);
260		token->type = SCHEMA_EQUALITY;
261
262		token->value = get_def_value(ctx, &c);
263
264		c = skip_spaces(c);
265		*string = c;
266		return token;
267	}
268
269	if (strcasecmp("ORDERING", type) == 0) {
270		talloc_free(type);
271		token->type = SCHEMA_ORDERING;
272
273		token->value = get_def_value(ctx, &c);
274
275		c = skip_spaces(c);
276		*string = c;
277		return token;
278	}
279
280	if (strcasecmp("SUBSTR", type) == 0) {
281		talloc_free(type);
282		token->type = SCHEMA_SUBSTR;
283
284		token->value = get_def_value(ctx, &c);
285
286		c = skip_spaces(c);
287		*string = c;
288		return token;
289	}
290
291	if (strcasecmp("SYNTAX", type) == 0) {
292		talloc_free(type);
293		token->type = SCHEMA_SYNTAX;
294
295		token->value = get_def_value(ctx, &c);
296
297		c = skip_spaces(c);
298		*string = c;
299		return token;
300	}
301
302	if (strcasecmp("DESC", type) == 0) {
303		talloc_free(type);
304		token->type = SCHEMA_DESC;
305
306		token->value = get_def_value(ctx, &c);
307
308		c = skip_spaces(c);
309		*string = c;
310		return token;
311	}
312
313	token->type = SCHEMA_UNKNOWN;
314	token->value = type;
315	if (*c == ')') {
316		*string = c;
317		return token;
318	}
319	if (*c == '\'') {
320		c = strchr(++c, '\'');
321		c++;
322	} else {
323		c += strcspn(c, " \t\n");
324	}
325	c = skip_spaces(c);
326	*string = c;
327
328	return token;
329}
330
331static struct ldb_message *process_entry(TALLOC_CTX *mem_ctx, const char *entry)
332{
333	TALLOC_CTX *ctx;
334	struct ldb_message *msg;
335	struct schema_token *token;
336        char *c, *s;
337        int n;
338
339	ctx = talloc_new(mem_ctx);
340	msg = ldb_msg_new(ctx);
341
342	ldb_msg_add_string(msg, "objectClass", "top");
343
344	c = talloc_strdup(ctx, entry);
345	if (!c) return NULL;
346
347	c = skip_spaces(c);
348
349	switch (*c) {
350	case 'a':
351		if (strncmp(c, "attributetype", 13) == 0) {
352			c += 13;
353			MSG_ADD_STRING("objectClass", "attributeSchema");
354			break;
355		}
356		goto failed;
357	case 'o':
358		if (strncmp(c, "objectclass", 11) == 0) {
359			c += 11;
360			MSG_ADD_STRING("objectClass", "classSchema");
361			break;
362		}
363		goto failed;
364	default:
365		goto failed;
366	}
367
368	c = strchr(c, '(');
369	if (c == NULL) goto failed;
370	c++;
371
372	c = skip_spaces(c);
373
374	/* get attributeID */
375	n = strcspn(c, " \t");
376	s = talloc_strndup(msg, c, n);
377	MSG_ADD_STRING("attributeID", s);
378	c += n;
379	c = skip_spaces(c);
380
381	while (*c != ')') {
382		token = get_next_schema_token(msg, &c);
383		if (!token) goto failed;
384
385		switch (token->type) {
386		case SCHEMA_NAME:
387			MSG_ADD_STRING("cn", token->value);
388			MSG_ADD_STRING("name", token->value);
389			MSG_ADD_STRING("lDAPDisplayName", token->value);
390			msg->dn = ldb_dn_copy(msg, basedn);
391			ldb_dn_add_child_fmt(msg->dn, "CN=%s,CN=Schema,CN=Configuration", token->value);
392			break;
393
394		case SCHEMA_SUP:
395			MSG_ADD_M_STRING("subClassOf", token->value);
396			break;
397
398		case SCHEMA_STRUCTURAL:
399			MSG_ADD_STRING("objectClassCategory", "1");
400			break;
401
402		case SCHEMA_ABSTRACT:
403			MSG_ADD_STRING("objectClassCategory", "2");
404			break;
405
406		case SCHEMA_AUXILIARY:
407			MSG_ADD_STRING("objectClassCategory", "3");
408			break;
409
410		case SCHEMA_MUST:
411			MSG_ADD_M_STRING("mustContain", token->value);
412			break;
413
414		case SCHEMA_MAY:
415			MSG_ADD_M_STRING("mayContain", token->value);
416			break;
417
418		case SCHEMA_SINGLE_VALUE:
419			MSG_ADD_STRING("isSingleValued", "TRUE");
420			break;
421
422		case SCHEMA_EQUALITY:
423			/* TODO */
424			break;
425
426		case SCHEMA_ORDERING:
427			/* TODO */
428			break;
429
430		case SCHEMA_SUBSTR:
431			/* TODO */
432			break;
433
434		case SCHEMA_SYNTAX:
435		{
436			const struct dsdb_syntax *map =
437				find_syntax_map_by_standard_oid(token->value);
438			if (!map) {
439				break;
440			}
441			MSG_ADD_STRING("attributeSyntax", map->attributeSyntax_oid);
442			break;
443		}
444		case SCHEMA_DESC:
445			MSG_ADD_STRING("description", token->value);
446			break;
447
448		default:
449			fprintf(stderr, "Unknown Definition: %s\n", token->value);
450		}
451	}
452
453	talloc_steal(mem_ctx, msg);
454	talloc_free(ctx);
455	return msg;
456
457failed:
458	talloc_free(ctx);
459	return NULL;
460}
461
462static struct schema_conv process_file(FILE *in, FILE *out)
463{
464	TALLOC_CTX *ctx;
465	struct schema_conv ret;
466	char *entry;
467	int c, t, line;
468	struct ldb_ldif ldif;
469
470	ldif.changetype = LDB_CHANGETYPE_NONE;
471
472	ctx = talloc_new(NULL);
473
474	ret.count = 0;
475	ret.failures = 0;
476	line = 0;
477
478	while ((c = fgetc(in)) != EOF) {
479		line++;
480		/* fprintf(stderr, "Parsing line %d\n", line); */
481		if (c == '#') {
482			do {
483				c = fgetc(in);
484			} while (c != EOF && c != '\n');
485			continue;
486		}
487		if (c == '\n') {
488			continue;
489		}
490
491		t = 0;
492		entry = talloc_array(ctx, char, 1024);
493		if (entry == NULL) exit(-1);
494
495		do {
496			if (c == '\n') {
497				entry[t] = '\0';
498				if (check_braces(entry) == 0) {
499					ret.count++;
500					ldif.msg = process_entry(ctx, entry);
501					if (ldif.msg == NULL) {
502						ret.failures++;
503						fprintf(stderr, "No valid msg from entry \n[%s]\n at line %d\n", entry, line);
504						break;
505					}
506					ldb_ldif_write_file(ldb_ctx, out, &ldif);
507					break;
508				}
509				line++;
510			} else {
511				entry[t] = c;
512				t++;
513			}
514			if ((t % 1023) == 0) {
515				entry = talloc_realloc(ctx, entry, char, t + 1024);
516				if (entry == NULL) exit(-1);
517			}
518		} while ((c = fgetc(in)) != EOF);
519
520		if (c != '\n') {
521			entry[t] = '\0';
522			if (check_braces(entry) == 0) {
523				ret.count++;
524				ldif.msg = process_entry(ctx, entry);
525				if (ldif.msg == NULL) {
526					ret.failures++;
527					fprintf(stderr, "No valid msg from entry \n[%s]\n at line %d\n", entry, line);
528					break;
529				}
530				ldb_ldif_write_file(ldb_ctx, out, &ldif);
531			} else {
532				fprintf(stderr, "malformed entry on line %d\n", line);
533				ret.failures++;
534			}
535		}
536
537		if (c == EOF) break;
538	}
539
540	return ret;
541}
542
543static void usage(void)
544{
545	printf("Usage: oLschema2ldif -H NONE <options>\n");
546	printf("\nConvert OpenLDAP schema to AD-like LDIF format\n\n");
547	printf("Options:\n");
548	printf("  -I inputfile     inputfile of OpenLDAP style schema otherwise STDIN\n");
549	printf("  -O outputfile    outputfile otherwise STDOUT\n");
550	printf("  -o options       pass options like modules to activate\n");
551	printf("              e.g: -o modules:timestamps\n");
552	printf("\n");
553	printf("Converts records from an openLdap formatted schema to an ldif schema\n\n");
554	exit(1);
555}
556
557 int main(int argc, const char **argv)
558{
559	TALLOC_CTX *ctx;
560	struct schema_conv ret;
561	struct ldb_cmdline *options;
562	FILE *in = stdin;
563	FILE *out = stdout;
564	ctx = talloc_new(NULL);
565	ldb_ctx = ldb_init(ctx, NULL);
566
567	setenv("LDB_URL", "NONE", 1);
568	options = ldb_cmdline_process(ldb_ctx, argc, argv, usage);
569
570	if (options->basedn == NULL) {
571		perror("Base DN not specified");
572		exit(1);
573	} else {
574		basedn = ldb_dn_new(ctx, ldb_ctx, options->basedn);
575		if ( ! ldb_dn_validate(basedn)) {
576			perror("Malformed Base DN");
577			exit(1);
578		}
579	}
580
581	if (options->input) {
582		in = fopen(options->input, "r");
583		if (!in) {
584			perror(options->input);
585			exit(1);
586		}
587	}
588	if (options->output) {
589		out = fopen(options->output, "w");
590		if (!out) {
591			perror(options->output);
592			exit(1);
593		}
594	}
595
596	ret = process_file(in, out);
597
598	fclose(in);
599	fclose(out);
600
601	printf("Converted %d records with %d failures\n", ret.count, ret.failures);
602
603	return 0;
604}
605