r22497: Support renaming objectclasses and attributes for the LDAP backend.
[Samba.git] / source / lib / ldb / tools / ad2oLschema.c
blob16e3c8941e0203c9e9917c01bfa41ac1bf832159
1 /*
2 ldb database library
4 Copyright (C) Andrew Bartlett 2006
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
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 2 of the License, or (at your option) any later version.
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.
20 You should have received a copy of the GNU Lesser General Public
21 License along with this library; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 * Name: ldb
28 * Component: ad2oLschema
30 * Description: utility to convert an AD schema into the format required by OpenLDAP
32 * Author: Andrew Tridgell
35 #include "includes.h"
36 #include "ldb/include/includes.h"
37 #include "system/locale.h"
38 #include "ldb/tools/cmdline.h"
39 #include "ldb/tools/convert.h"
41 struct schema_conv {
42 int count;
43 int skipped;
44 int failures;
47 enum convert_target {
48 TARGET_OPENLDAP,
49 TARGET_FEDORA_DS
53 static void usage(void)
55 printf("Usage: ad2oLschema <options>\n");
56 printf("\nConvert AD-like LDIF to OpenLDAP schema format\n\n");
57 printf("Options:\n");
58 printf(" -I inputfile inputfile of mapped OIDs and skipped attributes/ObjectClasses");
59 printf(" -H url LDB or LDAP server to read schmea from\n");
60 printf(" -O outputfile outputfile otherwise STDOUT\n");
61 printf(" -o options pass options like modules to activate\n");
62 printf(" e.g: -o modules:timestamps\n");
63 printf("\n");
64 printf("Converts records from an AD-like LDIF schema into an openLdap formatted schema\n\n");
65 exit(1);
68 static int fetch_attrs_schema(struct ldb_context *ldb, struct ldb_dn *schemadn,
69 TALLOC_CTX *mem_ctx,
70 struct ldb_result **attrs_res)
72 TALLOC_CTX *local_ctx = talloc_new(mem_ctx);
73 int ret;
74 const char *attrs[] = {
75 "lDAPDisplayName",
76 "isSingleValued",
77 "attributeID",
78 "attributeSyntax",
79 "description",
80 NULL
83 if (!local_ctx) {
84 return LDB_ERR_OPERATIONS_ERROR;
87 /* Downlaod schema */
88 ret = ldb_search(ldb, schemadn, LDB_SCOPE_SUBTREE,
89 "objectClass=attributeSchema",
90 attrs, attrs_res);
91 if (ret != LDB_SUCCESS) {
92 printf("Search failed: %s\n", ldb_errstring(ldb));
93 return LDB_ERR_OPERATIONS_ERROR;
96 return ret;
99 static const char *oc_attrs[] = {
100 "lDAPDisplayName",
101 "mayContain",
102 "mustContain",
103 "systemMayContain",
104 "systemMustContain",
105 "objectClassCategory",
106 "governsID",
107 "description",
108 "subClassOf",
109 NULL
112 static int fetch_oc_recursive(struct ldb_context *ldb, struct ldb_dn *schemadn,
113 TALLOC_CTX *mem_ctx,
114 struct ldb_result *search_from,
115 struct ldb_result *res_list)
117 int i;
118 int ret = 0;
119 for (i=0; i < search_from->count; i++) {
120 struct ldb_result *res;
121 const char *name = ldb_msg_find_attr_as_string(search_from->msgs[i],
122 "lDAPDisplayname", NULL);
124 ret = ldb_search_exp_fmt(ldb, mem_ctx, &res,
125 schemadn, LDB_SCOPE_SUBTREE, oc_attrs,
126 "(&(&(objectClass=classSchema)(subClassOf=%s))(!(lDAPDisplayName=%s)))",
127 name, name);
128 if (ret != LDB_SUCCESS) {
129 printf("Search failed: %s\n", ldb_errstring(ldb));
130 return ret;
133 res_list->msgs = talloc_realloc(res_list, res_list->msgs,
134 struct ldb_message *, res_list->count + 2);
135 if (!res_list->msgs) {
136 return LDB_ERR_OPERATIONS_ERROR;
138 res_list->msgs[res_list->count] = talloc_move(res_list,
139 &search_from->msgs[i]);
140 res_list->count++;
141 res_list->msgs[res_list->count] = NULL;
143 if (res->count > 0) {
144 ret = fetch_oc_recursive(ldb, schemadn, mem_ctx, res, res_list);
146 if (ret != LDB_SUCCESS) {
147 return ret;
150 return ret;
153 static int fetch_objectclass_schema(struct ldb_context *ldb, struct ldb_dn *schemadn,
154 TALLOC_CTX *mem_ctx,
155 struct ldb_result **objectclasses_res)
157 TALLOC_CTX *local_ctx = talloc_new(mem_ctx);
158 struct ldb_result *top_res, *ret_res;
159 int ret;
160 if (!local_ctx) {
161 return LDB_ERR_OPERATIONS_ERROR;
164 /* Downlaod 'top' */
165 ret = ldb_search(ldb, schemadn, LDB_SCOPE_SUBTREE,
166 "(&(objectClass=classSchema)(lDAPDisplayName=top))",
167 oc_attrs, &top_res);
168 if (ret != LDB_SUCCESS) {
169 printf("Search failed: %s\n", ldb_errstring(ldb));
170 return LDB_ERR_OPERATIONS_ERROR;
173 talloc_steal(local_ctx, top_res);
175 if (top_res->count != 1) {
176 return LDB_ERR_OPERATIONS_ERROR;
179 ret_res = talloc_zero(local_ctx, struct ldb_result);
180 if (!ret_res) {
181 return LDB_ERR_OPERATIONS_ERROR;
184 ret = fetch_oc_recursive(ldb, schemadn, local_ctx, top_res, ret_res);
186 if (ret != LDB_SUCCESS) {
187 printf("Search failed: %s\n", ldb_errstring(ldb));
188 return LDB_ERR_OPERATIONS_ERROR;
191 *objectclasses_res = talloc_move(mem_ctx, &ret_res);
192 return ret;
195 static struct ldb_dn *find_schema_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx)
197 const char *rootdse_attrs[] = {"schemaNamingContext", NULL};
198 struct ldb_dn *schemadn;
199 struct ldb_dn *basedn = ldb_dn_new(mem_ctx, ldb, NULL);
200 struct ldb_result *rootdse_res;
201 int ldb_ret;
202 if (!basedn) {
203 return NULL;
206 /* Search for rootdse */
207 ldb_ret = ldb_search(ldb, basedn, LDB_SCOPE_BASE, NULL, rootdse_attrs, &rootdse_res);
208 if (ldb_ret != LDB_SUCCESS) {
209 printf("Search failed: %s\n", ldb_errstring(ldb));
210 return NULL;
213 talloc_steal(mem_ctx, rootdse_res);
215 if (rootdse_res->count != 1) {
216 printf("Failed to find rootDSE");
217 return NULL;
220 /* Locate schema */
221 schemadn = ldb_msg_find_attr_as_dn(ldb, mem_ctx, rootdse_res->msgs[0], "schemaNamingContext");
222 if (!schemadn) {
223 return NULL;
226 talloc_free(rootdse_res);
227 return schemadn;
230 #define IF_NULL_FAIL_RET(x) do { \
231 if (!x) { \
232 ret.failures++; \
233 return ret; \
235 } while (0)
238 static struct schema_conv process_convert(struct ldb_context *ldb, enum convert_target target, FILE *in, FILE *out)
240 /* Read list of attributes to skip, OIDs to map */
241 TALLOC_CTX *mem_ctx = talloc_new(ldb);
242 char *line;
243 const char **attrs_skip = NULL;
244 int num_skip = 0;
245 struct oid_map {
246 char *old_oid;
247 char *new_oid;
248 } *oid_map = NULL;
249 int num_oid_maps = 0;
250 struct attr_map {
251 char *old_attr;
252 char *new_attr;
253 } *attr_map = NULL;
254 int num_attr_maps = 0;
255 struct ldb_result *attrs_res, *objectclasses_res;
256 struct ldb_dn *schemadn;
257 struct schema_conv ret;
259 int ldb_ret, i;
261 ret.count = 0;
262 ret.skipped = 0;
263 ret.failures = 0;
265 while ((line = afdgets(fileno(in), mem_ctx, 0))) {
266 /* Blank Line */
267 if (line[0] == '\0') {
268 continue;
270 /* Comment */
271 if (line[0] == '#') {
272 continue;
274 if (isdigit(line[0])) {
275 char *p = strchr(line, ':');
276 IF_NULL_FAIL_RET(p);
277 p[0] = '\0';
278 p++;
279 oid_map = talloc_realloc(mem_ctx, oid_map, struct oid_map, num_oid_maps + 2);
280 trim_string(line, " ", " ");
281 oid_map[num_oid_maps].old_oid = talloc_move(oid_map, &line);
282 trim_string(p, " ", " ");
283 oid_map[num_oid_maps].new_oid = p;
284 num_oid_maps++;
285 oid_map[num_oid_maps].old_oid = NULL;
286 } else {
287 char *p = strchr(line, ':');
288 if (p) {
289 /* remap attribute/objectClass */
290 p[0] = '\0';
291 p++;
292 attr_map = talloc_realloc(mem_ctx, attr_map, struct attr_map, num_attr_maps + 2);
293 trim_string(line, " ", " ");
294 attr_map[num_attr_maps].old_attr = talloc_move(attr_map, &line);
295 trim_string(p, " ", " ");
296 attr_map[num_attr_maps].new_attr = p;
297 num_attr_maps++;
298 attr_map[num_attr_maps].old_attr = NULL;
299 } else {
300 /* skip attribute/objectClass */
301 attrs_skip = talloc_realloc(mem_ctx, attrs_skip, const char *, num_skip + 2);
302 trim_string(line, " ", " ");
303 attrs_skip[num_skip] = talloc_move(attrs_skip, &line);
304 num_skip++;
305 attrs_skip[num_skip] = NULL;
310 schemadn = find_schema_dn(ldb, mem_ctx);
311 if (!schemadn) {
312 printf("Failed to find schema DN: %s\n", ldb_errstring(ldb));
313 ret.failures = 1;
314 return ret;
317 ldb_ret = fetch_attrs_schema(ldb, schemadn, mem_ctx, &attrs_res);
318 if (ldb_ret != LDB_SUCCESS) {
319 printf("Failed to fetch attribute schema: %s\n", ldb_errstring(ldb));
320 ret.failures = 1;
321 return ret;
324 switch (target) {
325 case TARGET_OPENLDAP:
326 break;
327 case TARGET_FEDORA_DS:
328 fprintf(out, "dn: cn=schema\n");
329 break;
332 for (i=0; i < attrs_res->count; i++) {
333 struct ldb_message *msg = attrs_res->msgs[i];
335 const char *name = ldb_msg_find_attr_as_string(msg, "lDAPDisplayName", NULL);
336 const char *description = ldb_msg_find_attr_as_string(msg, "description", NULL);
337 const char *oid = ldb_msg_find_attr_as_string(msg, "attributeID", NULL);
338 const char *syntax = ldb_msg_find_attr_as_string(msg, "attributeSyntax", NULL);
339 BOOL single_value = ldb_msg_find_attr_as_bool(msg, "isSingleValued", False);
340 const struct syntax_map *map = find_syntax_map_by_ad_oid(syntax);
341 char *schema_entry = NULL;
342 int j;
344 if (!name) {
345 printf("Failed to find lDAPDisplayName for schema DN: %s\n", ldb_dn_get_linearized(msg->dn));
346 ret.failures++;
347 continue;
350 /* We have been asked to skip some attributes/objectClasses */
351 if (attrs_skip && str_list_check_ci(attrs_skip, name)) {
352 ret.skipped++;
353 continue;
356 /* We might have been asked to remap this oid, due to a conflict */
357 for (j=0; oid && oid_map && oid_map[j].old_oid; j++) {
358 if (strcmp(oid, oid_map[j].old_oid) == 0) {
359 oid = oid_map[j].new_oid;
360 break;
364 switch (target) {
365 case TARGET_OPENLDAP:
366 schema_entry = talloc_asprintf(mem_ctx,
367 "attributetype (\n"
368 " %s\n", oid);
369 break;
370 case TARGET_FEDORA_DS:
371 schema_entry = talloc_asprintf(mem_ctx,
372 "attributeTypes: (\n"
373 " %s\n", oid);
374 break;
376 IF_NULL_FAIL_RET(schema_entry);
378 /* We might have been asked to remap this name, due to a conflict */
379 for (j=0; name && attr_map && attr_map[j].old_attr; j++) {
380 if (strcmp(name, attr_map[j].old_attr) == 0) {
381 name = attr_map[j].new_attr;
382 break;
386 schema_entry = talloc_asprintf_append(schema_entry,
387 " NAME '%s'\n", name);
388 IF_NULL_FAIL_RET(schema_entry);
390 if (description) {
391 schema_entry = talloc_asprintf_append(schema_entry,
392 " DESC %s\n", description);
393 IF_NULL_FAIL_RET(schema_entry);
396 if (map) {
397 const char *syntax_oid;
398 if (map->equality) {
399 schema_entry = talloc_asprintf_append(schema_entry,
400 " EQUALITY %s\n", map->equality);
401 IF_NULL_FAIL_RET(schema_entry);
403 if (map->substring) {
404 schema_entry = talloc_asprintf_append(schema_entry,
405 " SUBSTR %s\n", map->substring);
406 IF_NULL_FAIL_RET(schema_entry);
408 syntax_oid = map->Standard_OID;
409 /* We might have been asked to remap this oid,
410 * due to a conflict, or lack of
411 * implementation */
412 for (j=0; syntax_oid && oid_map[j].old_oid; j++) {
413 if (strcmp(syntax_oid, oid_map[j].old_oid) == 0) {
414 syntax_oid = oid_map[j].new_oid;
415 break;
418 schema_entry = talloc_asprintf_append(schema_entry,
419 " SYNTAX %s\n", syntax_oid);
420 IF_NULL_FAIL_RET(schema_entry);
423 if (single_value) {
424 schema_entry = talloc_asprintf_append(schema_entry,
425 " SINGLE-VALUE\n");
426 IF_NULL_FAIL_RET(schema_entry);
429 schema_entry = talloc_asprintf_append(schema_entry,
430 " )");
432 switch (target) {
433 case TARGET_OPENLDAP:
434 fprintf(out, "%s\n\n", schema_entry);
435 break;
436 case TARGET_FEDORA_DS:
437 fprintf(out, "%s\n", schema_entry);
438 break;
440 ret.count++;
443 ldb_ret = fetch_objectclass_schema(ldb, schemadn, mem_ctx, &objectclasses_res);
444 if (ldb_ret != LDB_SUCCESS) {
445 printf("Failed to fetch objectClass schema elements: %s\n", ldb_errstring(ldb));
446 ret.failures = 1;
447 return ret;
450 for (i=0; i < objectclasses_res->count; i++) {
451 struct ldb_message *msg = objectclasses_res->msgs[i];
452 const char *name = ldb_msg_find_attr_as_string(msg, "lDAPDisplayName", NULL);
453 const char *description = ldb_msg_find_attr_as_string(msg, "description", NULL);
454 const char *oid = ldb_msg_find_attr_as_string(msg, "governsID", NULL);
455 const char *subClassOf = ldb_msg_find_attr_as_string(msg, "subClassOf", NULL);
456 int objectClassCategory = ldb_msg_find_attr_as_int(msg, "objectClassCategory", 0);
457 struct ldb_message_element *must = ldb_msg_find_element(msg, "mustContain");
458 struct ldb_message_element *sys_must = ldb_msg_find_element(msg, "systemMustContain");
459 struct ldb_message_element *may = ldb_msg_find_element(msg, "mayContain");
460 struct ldb_message_element *sys_may = ldb_msg_find_element(msg, "systemMayContain");
461 char *schema_entry = NULL;
462 int j;
464 if (!name) {
465 printf("Failed to find lDAPDisplayName for schema DN: %s\n", ldb_dn_get_linearized(msg->dn));
466 ret.failures++;
467 continue;
470 /* We have been asked to skip some attributes/objectClasses */
471 if (attrs_skip && str_list_check_ci(attrs_skip, name)) {
472 ret.skipped++;
473 continue;
476 /* We might have been asked to remap this oid, due to a conflict */
477 for (j=0; oid_map[j].old_oid; j++) {
478 if (strcmp(oid, oid_map[j].old_oid) == 0) {
479 oid = oid_map[j].new_oid;
480 break;
484 switch (target) {
485 case TARGET_OPENLDAP:
486 schema_entry = talloc_asprintf(mem_ctx,
487 "objectclass (\n"
488 " %s\n", oid);
489 break;
490 case TARGET_FEDORA_DS:
491 schema_entry = talloc_asprintf(mem_ctx,
492 "objectClasses: (\n"
493 " %s\n", oid);
494 break;
496 IF_NULL_FAIL_RET(schema_entry);
497 if (!schema_entry) {
498 ret.failures++;
499 break;
502 /* We might have been asked to remap this name, due to a conflict */
503 for (j=0; name && attr_map && attr_map[j].old_attr; j++) {
504 if (strcmp(name, attr_map[j].old_attr) == 0) {
505 name = attr_map[j].new_attr;
506 break;
510 schema_entry = talloc_asprintf_append(schema_entry,
511 " NAME '%s'\n", name);
512 IF_NULL_FAIL_RET(schema_entry);
514 if (!schema_entry) return ret;
516 if (description) {
517 schema_entry = talloc_asprintf_append(schema_entry,
518 " DESC %s\n", description);
519 IF_NULL_FAIL_RET(schema_entry);
522 if (subClassOf) {
523 schema_entry = talloc_asprintf_append(schema_entry,
524 " SUP %s\n", subClassOf);
525 IF_NULL_FAIL_RET(schema_entry);
528 switch (objectClassCategory) {
529 case 1:
530 schema_entry = talloc_asprintf_append(schema_entry,
531 " STRUCTURAL\n");
532 IF_NULL_FAIL_RET(schema_entry);
533 break;
534 case 2:
535 schema_entry = talloc_asprintf_append(schema_entry,
536 " ABSTRACT\n");
537 IF_NULL_FAIL_RET(schema_entry);
538 break;
539 case 3:
540 schema_entry = talloc_asprintf_append(schema_entry,
541 " AUXILIARY\n");
542 IF_NULL_FAIL_RET(schema_entry);
543 break;
546 #define APPEND_ATTRS(attributes) \
547 do { \
548 int k; \
549 for (k=0; attributes && k < attributes->num_values; k++) { \
550 int attr_idx; \
551 const char *attr_name = (const char *)attributes->values[k].data; \
552 /* We might have been asked to remap this name, due to a conflict */ \
553 for (attr_idx=0; attr_name && attr_map && attr_map[attr_idx].old_attr; attr_idx++) { \
554 if (strcmp(attr_name, attr_map[attr_idx].old_attr) == 0) { \
555 attr_name = attr_map[attr_idx].new_attr; \
556 break; \
560 schema_entry = talloc_asprintf_append(schema_entry, \
561 " %s", \
562 attr_name); \
563 IF_NULL_FAIL_RET(schema_entry); \
564 if (k != (attributes->num_values - 1)) { \
565 schema_entry = talloc_asprintf_append(schema_entry, \
566 " $"); \
567 IF_NULL_FAIL_RET(schema_entry); \
568 if (target == TARGET_OPENLDAP && ((k+1)%5 == 0)) { \
569 schema_entry = talloc_asprintf_append(schema_entry, \
570 "\n "); \
571 IF_NULL_FAIL_RET(schema_entry); \
575 } while (0)
577 if (must || sys_must) {
578 schema_entry = talloc_asprintf_append(schema_entry,
579 " MUST (");
580 IF_NULL_FAIL_RET(schema_entry);
582 APPEND_ATTRS(must);
583 if (must && sys_must) {
584 schema_entry = talloc_asprintf_append(schema_entry, \
585 " $"); \
587 APPEND_ATTRS(sys_must);
589 schema_entry = talloc_asprintf_append(schema_entry,
590 " )\n");
591 IF_NULL_FAIL_RET(schema_entry);
594 if (may || sys_may) {
595 schema_entry = talloc_asprintf_append(schema_entry,
596 " MAY (");
597 IF_NULL_FAIL_RET(schema_entry);
599 APPEND_ATTRS(may);
600 if (may && sys_may) {
601 schema_entry = talloc_asprintf_append(schema_entry, \
602 " $"); \
604 APPEND_ATTRS(sys_may);
606 schema_entry = talloc_asprintf_append(schema_entry,
607 " )\n");
608 IF_NULL_FAIL_RET(schema_entry);
611 schema_entry = talloc_asprintf_append(schema_entry,
612 " )");
614 switch (target) {
615 case TARGET_OPENLDAP:
616 fprintf(out, "%s\n\n", schema_entry);
617 break;
618 case TARGET_FEDORA_DS:
619 fprintf(out, "%s\n", schema_entry);
620 break;
622 ret.count++;
625 return ret;
628 int main(int argc, const char **argv)
630 TALLOC_CTX *ctx;
631 struct ldb_cmdline *options;
632 FILE *in = stdin;
633 FILE *out = stdout;
634 struct ldb_context *ldb;
635 struct schema_conv ret;
636 const char *target_str;
637 enum convert_target target;
639 ldb_global_init();
641 ctx = talloc_new(NULL);
642 ldb = ldb_init(ctx);
644 options = ldb_cmdline_process(ldb, argc, argv, usage);
646 if (options->input) {
647 in = fopen(options->input, "r");
648 if (!in) {
649 perror(options->input);
650 exit(1);
653 if (options->output) {
654 out = fopen(options->output, "w");
655 if (!out) {
656 perror(options->output);
657 exit(1);
661 target_str = lp_parm_string(-1, "convert", "target");
663 if (!target_str || strcasecmp(target_str, "openldap") == 0) {
664 target = TARGET_OPENLDAP;
665 } else if (strcasecmp(target_str, "fedora-ds") == 0) {
666 target = TARGET_FEDORA_DS;
667 } else {
668 printf("Unsupported target: %s\n", target_str);
669 exit(1);
672 ret = process_convert(ldb, target, in, out);
674 fclose(in);
675 fclose(out);
677 printf("Converted %d records (skipped %d) with %d failures\n", ret.count, ret.skipped, ret.failures);
679 return 0;