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
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.
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, see <http://www.gnu.org/licenses/>.
27 * Component: ad2oLschema
29 * Description: utility to convert an AD schema into the format required by OpenLDAP
31 * Author: Andrew Bartlett
35 #include "ldb_includes.h"
36 #include "system/locale.h"
37 #include "tools/cmdline.h"
38 #include "tools/convert.h"
39 #include "param/param.h"
40 #include "lib/cmdline/popt_common.h"
54 static void usage(void)
56 printf("Usage: ad2oLschema <options>\n");
57 printf("\nConvert AD-like LDIF to OpenLDAP schema format\n\n");
59 printf(" -I inputfile inputfile of mapped OIDs and skipped attributes/ObjectClasses");
60 printf(" -H url LDB or LDAP server to read schmea from\n");
61 printf(" -O outputfile outputfile otherwise STDOUT\n");
62 printf(" -o options pass options like modules to activate\n");
63 printf(" e.g: -o modules:timestamps\n");
65 printf("Converts records from an AD-like LDIF schema into an openLdap formatted schema\n\n");
69 static int fetch_attrs_schema(struct ldb_context *ldb, struct ldb_dn *schemadn,
71 struct ldb_result **attrs_res)
73 TALLOC_CTX *local_ctx = talloc_new(mem_ctx);
75 const char *attrs[] = {
85 return LDB_ERR_OPERATIONS_ERROR;
89 ret = ldb_search(ldb, schemadn, LDB_SCOPE_SUBTREE,
90 "objectClass=attributeSchema",
92 if (ret != LDB_SUCCESS) {
93 printf("Search failed: %s\n", ldb_errstring(ldb));
94 return LDB_ERR_OPERATIONS_ERROR;
100 static const char *oc_attrs[] = {
106 "objectClassCategory",
110 "systemAuxiliaryClass",
115 static int fetch_oc_recursive(struct ldb_context *ldb, struct ldb_dn *schemadn,
117 struct ldb_result *search_from,
118 struct ldb_result *res_list)
122 for (i=0; i < search_from->count; i++) {
123 struct ldb_result *res;
124 const char *name = ldb_msg_find_attr_as_string(search_from->msgs[i],
125 "lDAPDisplayname", NULL);
127 ret = ldb_search_exp_fmt(ldb, mem_ctx, &res,
128 schemadn, LDB_SCOPE_SUBTREE, oc_attrs,
129 "(&(&(objectClass=classSchema)(subClassOf=%s))(!(lDAPDisplayName=%s)))",
131 if (ret != LDB_SUCCESS) {
132 printf("Search failed: %s\n", ldb_errstring(ldb));
136 res_list->msgs = talloc_realloc(res_list, res_list->msgs,
137 struct ldb_message *, res_list->count + 2);
138 if (!res_list->msgs) {
139 return LDB_ERR_OPERATIONS_ERROR;
141 res_list->msgs[res_list->count] = talloc_move(res_list,
142 &search_from->msgs[i]);
144 res_list->msgs[res_list->count] = NULL;
146 if (res->count > 0) {
147 ret = fetch_oc_recursive(ldb, schemadn, mem_ctx, res, res_list);
149 if (ret != LDB_SUCCESS) {
156 static int fetch_objectclass_schema(struct ldb_context *ldb, struct ldb_dn *schemadn,
158 struct ldb_result **objectclasses_res)
160 TALLOC_CTX *local_ctx = talloc_new(mem_ctx);
161 struct ldb_result *top_res, *ret_res;
164 return LDB_ERR_OPERATIONS_ERROR;
168 ret = ldb_search(ldb, schemadn, LDB_SCOPE_SUBTREE,
169 "(&(objectClass=classSchema)(lDAPDisplayName=top))",
171 if (ret != LDB_SUCCESS) {
172 printf("Search failed: %s\n", ldb_errstring(ldb));
173 return LDB_ERR_OPERATIONS_ERROR;
176 talloc_steal(local_ctx, top_res);
178 if (top_res->count != 1) {
179 return LDB_ERR_OPERATIONS_ERROR;
182 ret_res = talloc_zero(local_ctx, struct ldb_result);
184 return LDB_ERR_OPERATIONS_ERROR;
187 ret = fetch_oc_recursive(ldb, schemadn, local_ctx, top_res, ret_res);
189 if (ret != LDB_SUCCESS) {
190 printf("Search failed: %s\n", ldb_errstring(ldb));
191 return LDB_ERR_OPERATIONS_ERROR;
194 *objectclasses_res = talloc_move(mem_ctx, &ret_res);
198 static struct ldb_dn *find_schema_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx)
200 const char *rootdse_attrs[] = {"schemaNamingContext", NULL};
201 const char *no_attrs[] = { NULL };
202 struct ldb_dn *schemadn;
203 struct ldb_dn *basedn = ldb_dn_new(mem_ctx, ldb, NULL);
204 struct ldb_result *rootdse_res;
205 struct ldb_result *schema_res;
211 /* Search for rootdse */
212 ldb_ret = ldb_search(ldb, basedn, LDB_SCOPE_BASE, NULL, rootdse_attrs, &rootdse_res);
213 if (ldb_ret != LDB_SUCCESS) {
214 ldb_ret = ldb_search(ldb, basedn, LDB_SCOPE_SUBTREE,
215 "(&(objectClass=dMD)(cn=Schema))",
216 no_attrs, &schema_res);
218 printf("cn=Schema Search failed: %s\n", ldb_errstring(ldb));
222 talloc_steal(mem_ctx, schema_res);
224 if (schema_res->count != 1) {
225 printf("Failed to find rootDSE");
229 schemadn = talloc_steal(mem_ctx, schema_res->msgs[0]->dn);
230 talloc_free(schema_res);
235 talloc_steal(mem_ctx, rootdse_res);
237 if (rootdse_res->count != 1) {
238 printf("Failed to find rootDSE");
243 schemadn = ldb_msg_find_attr_as_dn(ldb, mem_ctx, rootdse_res->msgs[0], "schemaNamingContext");
248 talloc_free(rootdse_res);
252 static bool merge_attr_list(TALLOC_CTX *mem_ctx,
253 struct ldb_message_element *attrs, struct ldb_message_element *new_attrs)
255 struct ldb_val *values;
260 values = talloc_realloc(mem_ctx,
261 attrs->values, struct ldb_val, attrs->num_values + new_attrs->num_values);
263 attrs->values = values;
265 memcpy(&attrs->values[attrs->num_values], new_attrs->values, sizeof(*new_attrs->values) * new_attrs->num_values);
266 attrs->num_values = attrs->num_values + new_attrs->num_values;
268 /* Add sort and unique implementation here */
273 static bool find_aux_classes(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, struct ldb_dn *schema_dn,
274 struct ldb_message_element *aux_class, struct ldb_message_element *must,
275 struct ldb_message_element *sys_must, struct ldb_message_element *may,
276 struct ldb_message_element *sys_may)
279 struct ldb_message *msg;
280 struct ldb_result *res;
282 for (i=0; aux_class && i < aux_class->num_values; i++) {
283 ret = ldb_search_exp_fmt(ldb, mem_ctx, &res,
284 schema_dn, LDB_SCOPE_SUBTREE, oc_attrs,
285 "(&(objectClass=classSchema)(lDAPDisplayName=%s))",
286 aux_class->values[i].data);
287 if (ret != LDB_SUCCESS) {
293 if (!merge_attr_list(mem_ctx, must, ldb_msg_find_element(msg, "mustContain"))) {
296 if (!merge_attr_list(mem_ctx, sys_must, ldb_msg_find_element(msg, "systemMustContain"))) {
299 if (!merge_attr_list(mem_ctx, may, ldb_msg_find_element(msg, "mayContain"))) {
302 if (!merge_attr_list(mem_ctx, sys_may, ldb_msg_find_element(msg, "systemMayContain"))) {
307 if (res->count == 0) {
311 if (!find_aux_classes(mem_ctx, ldb, schema_dn,
312 ldb_msg_find_element(msg, "auxiliaryClass"), must, sys_must, may, sys_may)) {
315 if (!find_aux_classes(mem_ctx, ldb, schema_dn,
316 ldb_msg_find_element(msg, "systemAuxiliaryClass"), must, sys_must, may, sys_may)) {
324 #define IF_NULL_FAIL_RET(x) do { \
332 static struct schema_conv process_convert(struct ldb_context *ldb, enum convert_target target, FILE *in, FILE *out)
334 /* Read list of attributes to skip, OIDs to map */
335 TALLOC_CTX *mem_ctx = talloc_new(ldb);
337 const char **attrs_skip = NULL;
343 int num_oid_maps = 0;
348 int num_attr_maps = 0;
349 struct ldb_result *attrs_res, *objectclasses_res;
350 struct ldb_dn *schemadn;
351 struct schema_conv ret;
359 while ((line = afdgets(fileno(in), mem_ctx, 0))) {
361 if (line[0] == '\0') {
365 if (line[0] == '#') {
368 if (isdigit(line[0])) {
369 char *p = strchr(line, ':');
373 oid_map = talloc_realloc(mem_ctx, oid_map, struct oid_map, num_oid_maps + 2);
374 trim_string(line, " ", " ");
375 oid_map[num_oid_maps].old_oid = talloc_move(oid_map, &line);
376 trim_string(p, " ", " ");
377 oid_map[num_oid_maps].new_oid = p;
379 oid_map[num_oid_maps].old_oid = NULL;
381 char *p = strchr(line, ':');
383 /* remap attribute/objectClass */
386 attr_map = talloc_realloc(mem_ctx, attr_map, struct attr_map, num_attr_maps + 2);
387 trim_string(line, " ", " ");
388 attr_map[num_attr_maps].old_attr = talloc_move(attr_map, &line);
389 trim_string(p, " ", " ");
390 attr_map[num_attr_maps].new_attr = p;
392 attr_map[num_attr_maps].old_attr = NULL;
394 /* skip attribute/objectClass */
395 attrs_skip = talloc_realloc(mem_ctx, attrs_skip, const char *, num_skip + 2);
396 trim_string(line, " ", " ");
397 attrs_skip[num_skip] = talloc_move(attrs_skip, &line);
399 attrs_skip[num_skip] = NULL;
404 schemadn = find_schema_dn(ldb, mem_ctx);
406 printf("Failed to find schema DN: %s\n", ldb_errstring(ldb));
411 ldb_ret = fetch_attrs_schema(ldb, schemadn, mem_ctx, &attrs_res);
412 if (ldb_ret != LDB_SUCCESS) {
413 printf("Failed to fetch attribute schema: %s\n", ldb_errstring(ldb));
419 case TARGET_OPENLDAP:
421 case TARGET_FEDORA_DS:
422 fprintf(out, "dn: cn=schema\n");
426 for (i=0; i < attrs_res->count; i++) {
427 struct ldb_message *msg = attrs_res->msgs[i];
429 const char *name = ldb_msg_find_attr_as_string(msg, "lDAPDisplayName", NULL);
430 const char *description = ldb_msg_find_attr_as_string(msg, "description", NULL);
431 const char *oid = ldb_msg_find_attr_as_string(msg, "attributeID", NULL);
432 const char *syntax = ldb_msg_find_attr_as_string(msg, "attributeSyntax", NULL);
433 bool single_value = ldb_msg_find_attr_as_bool(msg, "isSingleValued", false);
434 const struct syntax_map *map = find_syntax_map_by_ad_oid(syntax);
435 char *schema_entry = NULL;
439 printf("Failed to find lDAPDisplayName for schema DN: %s\n", ldb_dn_get_linearized(msg->dn));
444 /* We have been asked to skip some attributes/objectClasses */
445 if (attrs_skip && str_list_check_ci(attrs_skip, name)) {
450 /* We might have been asked to remap this oid, due to a conflict */
451 for (j=0; oid && oid_map && oid_map[j].old_oid; j++) {
452 if (strcasecmp(oid, oid_map[j].old_oid) == 0) {
453 oid = oid_map[j].new_oid;
459 case TARGET_OPENLDAP:
460 schema_entry = talloc_asprintf(mem_ctx,
464 case TARGET_FEDORA_DS:
465 schema_entry = talloc_asprintf(mem_ctx,
466 "attributeTypes: (\n"
470 IF_NULL_FAIL_RET(schema_entry);
472 /* We might have been asked to remap this name, due to a conflict */
473 for (j=0; name && attr_map && attr_map[j].old_attr; j++) {
474 if (strcasecmp(name, attr_map[j].old_attr) == 0) {
475 name = attr_map[j].new_attr;
480 schema_entry = talloc_asprintf_append(schema_entry,
481 " NAME '%s'\n", name);
482 IF_NULL_FAIL_RET(schema_entry);
485 schema_entry = talloc_asprintf_append(schema_entry,
486 " DESC %s\n", description);
487 IF_NULL_FAIL_RET(schema_entry);
491 const char *syntax_oid;
493 schema_entry = talloc_asprintf_append(schema_entry,
494 " EQUALITY %s\n", map->equality);
495 IF_NULL_FAIL_RET(schema_entry);
497 if (map->substring) {
498 schema_entry = talloc_asprintf_append(schema_entry,
499 " SUBSTR %s\n", map->substring);
500 IF_NULL_FAIL_RET(schema_entry);
502 syntax_oid = map->Standard_OID;
503 /* We might have been asked to remap this oid,
504 * due to a conflict, or lack of
506 for (j=0; syntax_oid && oid_map && oid_map[j].old_oid; j++) {
507 if (strcasecmp(syntax_oid, oid_map[j].old_oid) == 0) {
508 syntax_oid = oid_map[j].new_oid;
512 schema_entry = talloc_asprintf_append(schema_entry,
513 " SYNTAX %s\n", syntax_oid);
514 IF_NULL_FAIL_RET(schema_entry);
518 schema_entry = talloc_asprintf_append(schema_entry,
520 IF_NULL_FAIL_RET(schema_entry);
523 schema_entry = talloc_asprintf_append(schema_entry,
527 case TARGET_OPENLDAP:
528 fprintf(out, "%s\n\n", schema_entry);
530 case TARGET_FEDORA_DS:
531 fprintf(out, "%s\n", schema_entry);
537 ldb_ret = fetch_objectclass_schema(ldb, schemadn, mem_ctx, &objectclasses_res);
538 if (ldb_ret != LDB_SUCCESS) {
539 printf("Failed to fetch objectClass schema elements: %s\n", ldb_errstring(ldb));
544 for (i=0; i < objectclasses_res->count; i++) {
545 struct ldb_message *msg = objectclasses_res->msgs[i];
546 const char *name = ldb_msg_find_attr_as_string(msg, "lDAPDisplayName", NULL);
547 const char *description = ldb_msg_find_attr_as_string(msg, "description", NULL);
548 const char *oid = ldb_msg_find_attr_as_string(msg, "governsID", NULL);
549 const char *subClassOf = ldb_msg_find_attr_as_string(msg, "subClassOf", NULL);
550 int objectClassCategory = ldb_msg_find_attr_as_int(msg, "objectClassCategory", 0);
551 struct ldb_message_element *must = ldb_msg_find_element(msg, "mustContain");
552 struct ldb_message_element *sys_must = ldb_msg_find_element(msg, "systemMustContain");
553 struct ldb_message_element *may = ldb_msg_find_element(msg, "mayContain");
554 struct ldb_message_element *sys_may = ldb_msg_find_element(msg, "systemMayContain");
555 struct ldb_message_element *aux_class = ldb_msg_find_element(msg, "auxiliaryClass");
556 struct ldb_message_element *sys_aux_class = ldb_msg_find_element(msg, "systemAuxiliaryClass");
557 char *schema_entry = NULL;
561 printf("Failed to find lDAPDisplayName for schema DN: %s\n", ldb_dn_get_linearized(msg->dn));
566 /* We have been asked to skip some attributes/objectClasses */
567 if (attrs_skip && str_list_check_ci(attrs_skip, name)) {
573 must = talloc_zero(mem_ctx, struct ldb_message_element);
577 may = talloc_zero(mem_ctx, struct ldb_message_element);
580 if (sys_must == NULL) {
581 sys_must = talloc_zero(mem_ctx, struct ldb_message_element);
584 if (sys_may == NULL) {
585 sys_may = talloc_zero(mem_ctx, struct ldb_message_element);
588 if (!find_aux_classes(mem_ctx, ldb, schemadn, aux_class, must, sys_must, may, sys_may)) {
593 if (!find_aux_classes(mem_ctx, ldb, schemadn, sys_aux_class, must, sys_must, may, sys_may)) {
598 /* We might have been asked to remap this oid, due to a conflict */
599 for (j=0; oid_map && oid_map[j].old_oid; j++) {
600 if (strcasecmp(oid, oid_map[j].old_oid) == 0) {
601 oid = oid_map[j].new_oid;
607 case TARGET_OPENLDAP:
608 schema_entry = talloc_asprintf(mem_ctx,
612 case TARGET_FEDORA_DS:
613 schema_entry = talloc_asprintf(mem_ctx,
618 IF_NULL_FAIL_RET(schema_entry);
624 /* We might have been asked to remap this name, due to a conflict */
625 for (j=0; name && attr_map && attr_map[j].old_attr; j++) {
626 if (strcasecmp(name, attr_map[j].old_attr) == 0) {
627 name = attr_map[j].new_attr;
632 schema_entry = talloc_asprintf_append(schema_entry,
633 " NAME '%s'\n", name);
634 IF_NULL_FAIL_RET(schema_entry);
636 if (!schema_entry) return ret;
639 schema_entry = talloc_asprintf_append(schema_entry,
640 " DESC %s\n", description);
641 IF_NULL_FAIL_RET(schema_entry);
645 schema_entry = talloc_asprintf_append(schema_entry,
646 " SUP %s\n", subClassOf);
647 IF_NULL_FAIL_RET(schema_entry);
650 switch (objectClassCategory) {
652 schema_entry = talloc_asprintf_append(schema_entry,
654 IF_NULL_FAIL_RET(schema_entry);
657 schema_entry = talloc_asprintf_append(schema_entry,
659 IF_NULL_FAIL_RET(schema_entry);
662 schema_entry = talloc_asprintf_append(schema_entry,
664 IF_NULL_FAIL_RET(schema_entry);
668 #define APPEND_ATTRS(attributes) \
671 for (k=0; attributes && k < attributes->num_values; k++) { \
673 const char *attr_name = (const char *)attributes->values[k].data; \
674 /* We might have been asked to remap this name, due to a conflict */ \
675 for (attr_idx=0; attr_name && attr_map && attr_map[attr_idx].old_attr; attr_idx++) { \
676 if (strcasecmp(attr_name, attr_map[attr_idx].old_attr) == 0) { \
677 attr_name = attr_map[attr_idx].new_attr; \
682 schema_entry = talloc_asprintf_append(schema_entry, \
685 IF_NULL_FAIL_RET(schema_entry); \
686 if (k != (attributes->num_values - 1)) { \
687 schema_entry = talloc_asprintf_append(schema_entry, \
689 IF_NULL_FAIL_RET(schema_entry); \
690 if (target == TARGET_OPENLDAP && ((k+1)%5 == 0)) { \
691 schema_entry = talloc_asprintf_append(schema_entry, \
693 IF_NULL_FAIL_RET(schema_entry); \
699 if ((must && must->values) || (sys_must && sys_must->values)) {
700 schema_entry = talloc_asprintf_append(schema_entry,
702 IF_NULL_FAIL_RET(schema_entry);
705 if (must && must->values && sys_must && sys_must->values) {
706 schema_entry = talloc_asprintf_append(schema_entry, \
709 APPEND_ATTRS(sys_must);
711 schema_entry = talloc_asprintf_append(schema_entry,
713 IF_NULL_FAIL_RET(schema_entry);
716 if ((may && may->values) || (sys_may && sys_may->values)) {
717 schema_entry = talloc_asprintf_append(schema_entry,
719 IF_NULL_FAIL_RET(schema_entry);
722 if (may && may->values && sys_may && sys_may->values) {
723 schema_entry = talloc_asprintf_append(schema_entry, \
726 APPEND_ATTRS(sys_may);
728 schema_entry = talloc_asprintf_append(schema_entry,
730 IF_NULL_FAIL_RET(schema_entry);
733 schema_entry = talloc_asprintf_append(schema_entry,
737 case TARGET_OPENLDAP:
738 fprintf(out, "%s\n\n", schema_entry);
740 case TARGET_FEDORA_DS:
741 fprintf(out, "%s\n", schema_entry);
750 int main(int argc, const char **argv)
753 struct ldb_cmdline *options;
756 struct ldb_context *ldb;
757 struct schema_conv ret;
758 const char *target_str;
759 enum convert_target target;
761 ctx = talloc_new(NULL);
762 ldb = ldb_init(ctx, NULL);
764 options = ldb_cmdline_process(ldb, argc, argv, usage);
766 if (options->input) {
767 in = fopen(options->input, "r");
769 perror(options->input);
773 if (options->output) {
774 out = fopen(options->output, "w");
776 perror(options->output);
781 target_str = lp_parm_string(cmdline_lp_ctx, NULL, "convert", "target");
783 if (!target_str || strcasecmp(target_str, "openldap") == 0) {
784 target = TARGET_OPENLDAP;
785 } else if (strcasecmp(target_str, "fedora-ds") == 0) {
786 target = TARGET_FEDORA_DS;
788 printf("Unsupported target: %s\n", target_str);
792 ret = process_convert(ldb, target, in, out);
797 printf("Converted %d records (skipped %d) with %d failures\n", ret.count, ret.skipped, ret.failures);