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 "lib/ldb/tools/cmdline.h"
38 #include "utils/schema_convert.h"
39 #include "param/param.h"
40 #include "lib/cmdline/popt_common.h"
41 #include "dsdb/samdb/samdb.h"
55 static void usage(void)
57 printf("Usage: ad2oLschema <options>\n");
58 printf("\nConvert AD-like LDIF to OpenLDAP schema format\n\n");
60 printf(" -I inputfile inputfile of mapped OIDs and skipped attributes/ObjectClasses");
61 printf(" -H url LDB or LDAP server to read schmea from\n");
62 printf(" -O outputfile outputfile otherwise STDOUT\n");
63 printf(" -o options pass options like modules to activate\n");
64 printf(" e.g: -o modules:timestamps\n");
66 printf("Converts records from an AD-like LDIF schema into an openLdap formatted schema\n\n");
70 static int fetch_attrs_schema(struct ldb_context *ldb, struct ldb_dn *schemadn,
72 struct ldb_result **attrs_res)
77 ret = ldb_search(ldb, schemadn, LDB_SCOPE_SUBTREE,
78 "objectClass=attributeSchema",
80 if (ret != LDB_SUCCESS) {
81 printf("Search failed: %s\n", ldb_errstring(ldb));
82 return LDB_ERR_OPERATIONS_ERROR;
88 static int fetch_oc_recursive(struct ldb_context *ldb, struct ldb_dn *schemadn,
90 struct ldb_result *search_from,
91 struct ldb_result *res_list)
95 for (i=0; i < search_from->count; i++) {
96 struct ldb_result *res;
97 const char *name = ldb_msg_find_attr_as_string(search_from->msgs[i],
98 "lDAPDisplayname", NULL);
100 ret = ldb_search_exp_fmt(ldb, mem_ctx, &res,
101 schemadn, LDB_SCOPE_SUBTREE, NULL,
102 "(&(&(objectClass=classSchema)(subClassOf=%s))(!(lDAPDisplayName=%s)))",
104 if (ret != LDB_SUCCESS) {
105 printf("Search failed: %s\n", ldb_errstring(ldb));
109 res_list->msgs = talloc_realloc(res_list, res_list->msgs,
110 struct ldb_message *, res_list->count + 2);
111 if (!res_list->msgs) {
112 return LDB_ERR_OPERATIONS_ERROR;
114 res_list->msgs[res_list->count] = talloc_move(res_list,
115 &search_from->msgs[i]);
117 res_list->msgs[res_list->count] = NULL;
119 if (res->count > 0) {
120 ret = fetch_oc_recursive(ldb, schemadn, mem_ctx, res, res_list);
122 if (ret != LDB_SUCCESS) {
129 static int fetch_objectclass_schema(struct ldb_context *ldb, struct ldb_dn *schemadn,
131 struct ldb_result **objectclasses_res)
133 TALLOC_CTX *local_ctx = talloc_new(mem_ctx);
134 struct ldb_result *top_res, *ret_res;
137 return LDB_ERR_OPERATIONS_ERROR;
141 ret = ldb_search(ldb, schemadn, LDB_SCOPE_SUBTREE,
142 "(&(objectClass=classSchema)(lDAPDisplayName=top))",
144 if (ret != LDB_SUCCESS) {
145 printf("Search failed: %s\n", ldb_errstring(ldb));
146 return LDB_ERR_OPERATIONS_ERROR;
149 talloc_steal(local_ctx, top_res);
151 if (top_res->count != 1) {
152 return LDB_ERR_OPERATIONS_ERROR;
155 ret_res = talloc_zero(local_ctx, struct ldb_result);
157 return LDB_ERR_OPERATIONS_ERROR;
160 ret = fetch_oc_recursive(ldb, schemadn, local_ctx, top_res, ret_res);
162 if (ret != LDB_SUCCESS) {
163 printf("Search failed: %s\n", ldb_errstring(ldb));
164 return LDB_ERR_OPERATIONS_ERROR;
167 *objectclasses_res = talloc_move(mem_ctx, &ret_res);
171 static struct ldb_dn *find_schema_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
172 struct ldb_result **schema_res)
174 const char *rootdse_attrs[] = {"schemaNamingContext", NULL};
175 struct ldb_dn *schemadn;
176 struct ldb_dn *basedn = ldb_dn_new(mem_ctx, ldb, NULL);
177 struct ldb_result *rootdse_res;
184 /* Search for rootdse */
185 ldb_ret = ldb_search(ldb, basedn, LDB_SCOPE_BASE, NULL, rootdse_attrs, &rootdse_res);
186 if (ldb_ret != LDB_SUCCESS) {
187 ldb_ret = ldb_search(ldb, basedn, LDB_SCOPE_SUBTREE,
188 "(&(objectClass=dMD)(cn=Schema))",
191 printf("cn=Schema Search failed: %s\n", ldb_errstring(ldb));
195 talloc_steal(mem_ctx, *schema_res);
197 if ((*schema_res)->count != 1) {
198 printf("Failed to find rootDSE");
202 schemadn = talloc_steal(mem_ctx, (*schema_res)->msgs[0]->dn);
207 talloc_steal(mem_ctx, rootdse_res);
209 if (rootdse_res->count != 1) {
210 printf("Failed to find rootDSE");
215 schemadn = ldb_msg_find_attr_as_dn(ldb, mem_ctx, rootdse_res->msgs[0], "schemaNamingContext");
220 ldb_ret = ldb_search(ldb, schemadn, LDB_SCOPE_BASE,
221 "(&(objectClass=dMD)(cn=Schema))",
224 printf("cn=Schema Search failed: %s\n", ldb_errstring(ldb));
228 talloc_steal(mem_ctx, *schema_res);
230 if ((*schema_res)->count != 1) {
231 printf("Failed to find rootDSE");
234 talloc_free(rootdse_res);
238 static bool merge_attr_list(TALLOC_CTX *mem_ctx,
239 struct ldb_message_element *attrs, struct ldb_message_element *new_attrs)
241 struct ldb_val *values;
246 values = talloc_realloc(mem_ctx,
247 attrs->values, struct ldb_val, attrs->num_values + new_attrs->num_values);
249 attrs->values = values;
251 memcpy(&attrs->values[attrs->num_values], new_attrs->values, sizeof(*new_attrs->values) * new_attrs->num_values);
252 attrs->num_values = attrs->num_values + new_attrs->num_values;
254 /* Add sort and unique implementation here */
259 static bool find_aux_classes(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, struct ldb_dn *schema_dn,
260 struct ldb_message_element *aux_class, struct ldb_message_element *must,
261 struct ldb_message_element *sys_must, struct ldb_message_element *may,
262 struct ldb_message_element *sys_may)
265 struct ldb_message *msg;
266 struct ldb_result *res;
268 for (i=0; aux_class && i < aux_class->num_values; i++) {
269 ret = ldb_search_exp_fmt(ldb, mem_ctx, &res,
270 schema_dn, LDB_SCOPE_SUBTREE, NULL,
271 "(&(objectClass=classSchema)(lDAPDisplayName=%s))",
272 aux_class->values[i].data);
273 if (ret != LDB_SUCCESS) {
279 if (!merge_attr_list(mem_ctx, must, ldb_msg_find_element(msg, "mustContain"))) {
282 if (!merge_attr_list(mem_ctx, sys_must, ldb_msg_find_element(msg, "systemMustContain"))) {
285 if (!merge_attr_list(mem_ctx, may, ldb_msg_find_element(msg, "mayContain"))) {
288 if (!merge_attr_list(mem_ctx, sys_may, ldb_msg_find_element(msg, "systemMayContain"))) {
293 if (res->count == 0) {
297 if (!find_aux_classes(mem_ctx, ldb, schema_dn,
298 ldb_msg_find_element(msg, "auxiliaryClass"), must, sys_must, may, sys_may)) {
301 if (!find_aux_classes(mem_ctx, ldb, schema_dn,
302 ldb_msg_find_element(msg, "systemAuxiliaryClass"), must, sys_must, may, sys_may)) {
310 #define IF_NULL_FAIL_RET(x) do { \
318 static struct schema_conv process_convert(struct ldb_context *ldb, enum convert_target target, FILE *in, FILE *out)
320 /* Read list of attributes to skip, OIDs to map */
321 TALLOC_CTX *mem_ctx = talloc_new(ldb);
323 const char **attrs_skip = NULL;
329 int num_oid_maps = 0;
334 int num_attr_maps = 0;
335 struct ldb_result *schema_res, *attrs_res, *objectclasses_res;
336 struct ldb_dn *schemadn;
337 struct schema_conv ret;
338 struct dsdb_schema *schema;
347 while ((line = afdgets(fileno(in), mem_ctx, 0))) {
349 if (line[0] == '\0') {
353 if (line[0] == '#') {
356 if (isdigit(line[0])) {
357 char *p = strchr(line, ':');
361 oid_map = talloc_realloc(mem_ctx, oid_map, struct oid_map, num_oid_maps + 2);
362 trim_string(line, " ", " ");
363 oid_map[num_oid_maps].old_oid = talloc_move(oid_map, &line);
364 trim_string(p, " ", " ");
365 oid_map[num_oid_maps].new_oid = p;
367 oid_map[num_oid_maps].old_oid = NULL;
369 char *p = strchr(line, ':');
371 /* remap attribute/objectClass */
374 attr_map = talloc_realloc(mem_ctx, attr_map, struct attr_map, num_attr_maps + 2);
375 trim_string(line, " ", " ");
376 attr_map[num_attr_maps].old_attr = talloc_move(attr_map, &line);
377 trim_string(p, " ", " ");
378 attr_map[num_attr_maps].new_attr = p;
380 attr_map[num_attr_maps].old_attr = NULL;
382 /* skip attribute/objectClass */
383 attrs_skip = talloc_realloc(mem_ctx, attrs_skip, const char *, num_skip + 2);
384 trim_string(line, " ", " ");
385 attrs_skip[num_skip] = talloc_move(attrs_skip, &line);
387 attrs_skip[num_skip] = NULL;
392 schemadn = find_schema_dn(ldb, mem_ctx, &schema_res);
394 printf("Failed to find schema DN: %s\n", ldb_errstring(ldb));
399 ldb_ret = fetch_attrs_schema(ldb, schemadn, mem_ctx, &attrs_res);
400 if (ldb_ret != LDB_SUCCESS) {
401 printf("Failed to fetch attribute schema: %s\n", ldb_errstring(ldb));
407 ldb_ret = fetch_objectclass_schema(ldb, schemadn, mem_ctx, &objectclasses_res);
408 if (ldb_ret != LDB_SUCCESS) {
409 printf("Failed to fetch objectClass schema elements: %s\n", ldb_errstring(ldb));
414 ldb_ret = dsdb_schema_from_ldb_results(mem_ctx, ldb,
415 lp_iconv_convenience(cmdline_lp_ctx),
416 schema_res, attrs_res, objectclasses_res, &schema, &error_string);
417 if (ldb_ret != LDB_SUCCESS) {
418 printf("Failed to load schema: %s\n", error_string);
424 case TARGET_OPENLDAP:
426 case TARGET_FEDORA_DS:
427 fprintf(out, "dn: cn=schema\n");
431 for (i=0; i < attrs_res->count; i++) {
432 struct ldb_message *msg = attrs_res->msgs[i];
434 const char *name = ldb_msg_find_attr_as_string(msg, "lDAPDisplayName", NULL);
435 const char *description = ldb_msg_find_attr_as_string(msg, "description", NULL);
436 const char *oid = ldb_msg_find_attr_as_string(msg, "attributeID", NULL);
437 const char *syntax = ldb_msg_find_attr_as_string(msg, "attributeSyntax", NULL);
438 bool single_value = ldb_msg_find_attr_as_bool(msg, "isSingleValued", false);
439 const struct syntax_map *map = find_syntax_map_by_ad_oid(syntax);
440 char *schema_entry = NULL;
444 printf("Failed to find lDAPDisplayName for schema DN: %s\n", ldb_dn_get_linearized(msg->dn));
449 /* We have been asked to skip some attributes/objectClasses */
450 if (attrs_skip && str_list_check_ci(attrs_skip, name)) {
455 /* We might have been asked to remap this oid, due to a conflict */
456 for (j=0; oid && oid_map && oid_map[j].old_oid; j++) {
457 if (strcasecmp(oid, oid_map[j].old_oid) == 0) {
458 oid = oid_map[j].new_oid;
464 case TARGET_OPENLDAP:
465 schema_entry = talloc_asprintf(mem_ctx,
469 case TARGET_FEDORA_DS:
470 schema_entry = talloc_asprintf(mem_ctx,
471 "attributeTypes: (\n"
475 IF_NULL_FAIL_RET(schema_entry);
477 /* We might have been asked to remap this name, due to a conflict */
478 for (j=0; name && attr_map && attr_map[j].old_attr; j++) {
479 if (strcasecmp(name, attr_map[j].old_attr) == 0) {
480 name = attr_map[j].new_attr;
485 schema_entry = talloc_asprintf_append(schema_entry,
486 " NAME '%s'\n", name);
487 IF_NULL_FAIL_RET(schema_entry);
490 schema_entry = talloc_asprintf_append(schema_entry,
491 " DESC %s\n", description);
492 IF_NULL_FAIL_RET(schema_entry);
496 const char *syntax_oid;
498 schema_entry = talloc_asprintf_append(schema_entry,
499 " EQUALITY %s\n", map->equality);
500 IF_NULL_FAIL_RET(schema_entry);
502 if (map->substring) {
503 schema_entry = talloc_asprintf_append(schema_entry,
504 " SUBSTR %s\n", map->substring);
505 IF_NULL_FAIL_RET(schema_entry);
507 syntax_oid = map->Standard_OID;
508 /* We might have been asked to remap this oid,
509 * due to a conflict, or lack of
511 for (j=0; syntax_oid && oid_map && oid_map[j].old_oid; j++) {
512 if (strcasecmp(syntax_oid, oid_map[j].old_oid) == 0) {
513 syntax_oid = oid_map[j].new_oid;
517 schema_entry = talloc_asprintf_append(schema_entry,
518 " SYNTAX %s\n", syntax_oid);
519 IF_NULL_FAIL_RET(schema_entry);
523 schema_entry = talloc_asprintf_append(schema_entry,
525 IF_NULL_FAIL_RET(schema_entry);
528 schema_entry = talloc_asprintf_append(schema_entry,
532 case TARGET_OPENLDAP:
533 fprintf(out, "%s\n\n", schema_entry);
535 case TARGET_FEDORA_DS:
536 fprintf(out, "%s\n", schema_entry);
542 for (i=0; i < objectclasses_res->count; i++) {
543 struct ldb_message *msg = objectclasses_res->msgs[i];
544 const char *name = ldb_msg_find_attr_as_string(msg, "lDAPDisplayName", NULL);
545 const char *description = ldb_msg_find_attr_as_string(msg, "description", NULL);
546 const char *oid = ldb_msg_find_attr_as_string(msg, "governsID", NULL);
547 const char *subClassOf = ldb_msg_find_attr_as_string(msg, "subClassOf", NULL);
548 int objectClassCategory = ldb_msg_find_attr_as_int(msg, "objectClassCategory", 0);
549 struct ldb_message_element *must = ldb_msg_find_element(msg, "mustContain");
550 struct ldb_message_element *sys_must = ldb_msg_find_element(msg, "systemMustContain");
551 struct ldb_message_element *may = ldb_msg_find_element(msg, "mayContain");
552 struct ldb_message_element *sys_may = ldb_msg_find_element(msg, "systemMayContain");
553 struct ldb_message_element *aux_class = ldb_msg_find_element(msg, "auxiliaryClass");
554 struct ldb_message_element *sys_aux_class = ldb_msg_find_element(msg, "systemAuxiliaryClass");
555 char *schema_entry = NULL;
559 printf("Failed to find lDAPDisplayName for schema DN: %s\n", ldb_dn_get_linearized(msg->dn));
564 /* We have been asked to skip some attributes/objectClasses */
565 if (attrs_skip && str_list_check_ci(attrs_skip, name)) {
571 must = talloc_zero(mem_ctx, struct ldb_message_element);
575 may = talloc_zero(mem_ctx, struct ldb_message_element);
578 if (sys_must == NULL) {
579 sys_must = talloc_zero(mem_ctx, struct ldb_message_element);
582 if (sys_may == NULL) {
583 sys_may = talloc_zero(mem_ctx, struct ldb_message_element);
586 if (!find_aux_classes(mem_ctx, ldb, schemadn, aux_class, must, sys_must, may, sys_may)) {
591 if (!find_aux_classes(mem_ctx, ldb, schemadn, sys_aux_class, must, sys_must, may, sys_may)) {
596 /* We might have been asked to remap this oid, due to a conflict */
597 for (j=0; oid_map && oid_map[j].old_oid; j++) {
598 if (strcasecmp(oid, oid_map[j].old_oid) == 0) {
599 oid = oid_map[j].new_oid;
605 case TARGET_OPENLDAP:
606 schema_entry = talloc_asprintf(mem_ctx,
610 case TARGET_FEDORA_DS:
611 schema_entry = talloc_asprintf(mem_ctx,
616 IF_NULL_FAIL_RET(schema_entry);
622 /* We might have been asked to remap this name, due to a conflict */
623 for (j=0; name && attr_map && attr_map[j].old_attr; j++) {
624 if (strcasecmp(name, attr_map[j].old_attr) == 0) {
625 name = attr_map[j].new_attr;
630 schema_entry = talloc_asprintf_append(schema_entry,
631 " NAME '%s'\n", name);
632 IF_NULL_FAIL_RET(schema_entry);
634 if (!schema_entry) return ret;
637 schema_entry = talloc_asprintf_append(schema_entry,
638 " DESC %s\n", description);
639 IF_NULL_FAIL_RET(schema_entry);
643 schema_entry = talloc_asprintf_append(schema_entry,
644 " SUP %s\n", subClassOf);
645 IF_NULL_FAIL_RET(schema_entry);
648 switch (objectClassCategory) {
650 schema_entry = talloc_asprintf_append(schema_entry,
652 IF_NULL_FAIL_RET(schema_entry);
655 schema_entry = talloc_asprintf_append(schema_entry,
657 IF_NULL_FAIL_RET(schema_entry);
660 schema_entry = talloc_asprintf_append(schema_entry,
662 IF_NULL_FAIL_RET(schema_entry);
666 #define APPEND_ATTRS(attributes) \
669 for (k=0; attributes && k < attributes->num_values; k++) { \
671 const char *attr_name = (const char *)attributes->values[k].data; \
672 /* We might have been asked to remap this name, due to a conflict */ \
673 for (attr_idx=0; attr_name && attr_map && attr_map[attr_idx].old_attr; attr_idx++) { \
674 if (strcasecmp(attr_name, attr_map[attr_idx].old_attr) == 0) { \
675 attr_name = attr_map[attr_idx].new_attr; \
680 schema_entry = talloc_asprintf_append(schema_entry, \
683 IF_NULL_FAIL_RET(schema_entry); \
684 if (k != (attributes->num_values - 1)) { \
685 schema_entry = talloc_asprintf_append(schema_entry, \
687 IF_NULL_FAIL_RET(schema_entry); \
688 if (target == TARGET_OPENLDAP && ((k+1)%5 == 0)) { \
689 schema_entry = talloc_asprintf_append(schema_entry, \
691 IF_NULL_FAIL_RET(schema_entry); \
697 if ((must && must->values) || (sys_must && sys_must->values)) {
698 schema_entry = talloc_asprintf_append(schema_entry,
700 IF_NULL_FAIL_RET(schema_entry);
703 if (must && must->values && sys_must && sys_must->values) {
704 schema_entry = talloc_asprintf_append(schema_entry, \
707 APPEND_ATTRS(sys_must);
709 schema_entry = talloc_asprintf_append(schema_entry,
711 IF_NULL_FAIL_RET(schema_entry);
714 if ((may && may->values) || (sys_may && sys_may->values)) {
715 schema_entry = talloc_asprintf_append(schema_entry,
717 IF_NULL_FAIL_RET(schema_entry);
720 if (may && may->values && sys_may && sys_may->values) {
721 schema_entry = talloc_asprintf_append(schema_entry, \
724 APPEND_ATTRS(sys_may);
726 schema_entry = talloc_asprintf_append(schema_entry,
728 IF_NULL_FAIL_RET(schema_entry);
731 schema_entry = talloc_asprintf_append(schema_entry,
735 case TARGET_OPENLDAP:
736 fprintf(out, "%s\n\n", schema_entry);
738 case TARGET_FEDORA_DS:
739 fprintf(out, "%s\n", schema_entry);
748 int main(int argc, const char **argv)
751 struct ldb_cmdline *options;
754 struct ldb_context *ldb;
755 struct schema_conv ret;
756 const char *target_str;
757 enum convert_target target;
759 ctx = talloc_new(NULL);
760 ldb = ldb_init(ctx, NULL);
762 options = ldb_cmdline_process(ldb, argc, argv, usage);
764 if (options->input) {
765 in = fopen(options->input, "r");
767 perror(options->input);
771 if (options->output) {
772 out = fopen(options->output, "w");
774 perror(options->output);
779 target_str = lp_parm_string(cmdline_lp_ctx, NULL, "convert", "target");
781 if (!target_str || strcasecmp(target_str, "openldap") == 0) {
782 target = TARGET_OPENLDAP;
783 } else if (strcasecmp(target_str, "fedora-ds") == 0) {
784 target = TARGET_FEDORA_DS;
786 printf("Unsupported target: %s\n", target_str);
790 ret = process_convert(ldb, target, in, out);
795 printf("Converted %d records (skipped %d) with %d failures\n", ret.count, ret.skipped, ret.failures);