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 struct ldb_dn *find_schema_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx)
72 const char *rootdse_attrs[] = {"schemaNamingContext", NULL};
73 struct ldb_dn *schemadn;
74 struct ldb_dn *basedn = ldb_dn_new(mem_ctx, ldb, NULL);
75 struct ldb_result *rootdse_res;
76 struct ldb_result *schema_res;
83 /* Search for rootdse */
84 ldb_ret = ldb_search(ldb, basedn, LDB_SCOPE_BASE, NULL, rootdse_attrs, &rootdse_res);
85 if (ldb_ret != LDB_SUCCESS) {
86 ldb_ret = ldb_search(ldb, basedn, LDB_SCOPE_SUBTREE,
87 "(&(objectClass=dMD)(cn=Schema))",
90 printf("cn=Schema Search failed: %s\n", ldb_errstring(ldb));
94 talloc_steal(mem_ctx, schema_res);
96 if (schema_res->count != 1) {
97 talloc_free(schema_res);
98 printf("Failed to find rootDSE");
102 schemadn = talloc_steal(mem_ctx, schema_res->msgs[0]->dn);
103 talloc_free(schema_res);
107 if (rootdse_res->count != 1) {
108 printf("Failed to find rootDSE");
109 talloc_free(rootdse_res);
114 schemadn = ldb_msg_find_attr_as_dn(ldb, mem_ctx, rootdse_res->msgs[0], "schemaNamingContext");
115 talloc_free(rootdse_res);
125 #define IF_NULL_FAIL_RET(x) do { \
133 static struct schema_conv process_convert(struct ldb_context *ldb, enum convert_target target, FILE *in, FILE *out)
135 /* Read list of attributes to skip, OIDs to map */
136 TALLOC_CTX *mem_ctx = talloc_new(ldb);
138 const char **attrs_skip = NULL;
144 int num_oid_maps = 0;
149 int num_attr_maps = 0;
150 struct dsdb_class *objectclass;
151 struct dsdb_attribute *attribute;
152 struct ldb_dn *schemadn;
153 struct schema_conv ret;
154 struct dsdb_schema *schema;
155 const char *seperator;
164 while ((line = afdgets(fileno(in), mem_ctx, 0))) {
166 if (line[0] == '\0') {
170 if (line[0] == '#') {
173 if (isdigit(line[0])) {
174 char *p = strchr(line, ':');
178 oid_map = talloc_realloc(mem_ctx, oid_map, struct oid_map, num_oid_maps + 2);
179 trim_string(line, " ", " ");
180 oid_map[num_oid_maps].old_oid = talloc_move(oid_map, &line);
181 trim_string(p, " ", " ");
182 oid_map[num_oid_maps].new_oid = p;
184 oid_map[num_oid_maps].old_oid = NULL;
186 char *p = strchr(line, ':');
188 /* remap attribute/objectClass */
191 attr_map = talloc_realloc(mem_ctx, attr_map, struct attr_map, num_attr_maps + 2);
192 trim_string(line, " ", " ");
193 attr_map[num_attr_maps].old_attr = talloc_move(attr_map, &line);
194 trim_string(p, " ", " ");
195 attr_map[num_attr_maps].new_attr = p;
197 attr_map[num_attr_maps].old_attr = NULL;
199 /* skip attribute/objectClass */
200 attrs_skip = talloc_realloc(mem_ctx, attrs_skip, const char *, num_skip + 2);
201 trim_string(line, " ", " ");
202 attrs_skip[num_skip] = talloc_move(attrs_skip, &line);
204 attrs_skip[num_skip] = NULL;
209 schemadn = find_schema_dn(ldb, mem_ctx);
211 printf("Failed to find schema DN: %s\n", ldb_errstring(ldb));
216 ldb_ret = dsdb_schema_from_schema_dn(mem_ctx, ldb,
217 lp_iconv_convenience(cmdline_lp_ctx),
218 schemadn, &schema, &error_string);
219 if (ldb_ret != LDB_SUCCESS) {
220 printf("Failed to load schema: %s\n", error_string);
226 case TARGET_OPENLDAP:
229 case TARGET_FEDORA_DS:
231 fprintf(out, "dn: cn=schema\n");
235 for (attribute=schema->attributes; attribute; attribute = attribute->next) {
236 const char *name = attribute->lDAPDisplayName;
237 const char *description = attribute->adminDescription;
238 const char *oid = attribute->attributeID_oid;
239 const char *syntax = attribute->attributeSyntax_oid;
240 bool single_value = attribute->isSingleValued;
242 const struct syntax_map *const_map = find_syntax_map_by_ad_oid(syntax);
243 struct syntax_map map, *map_p = NULL;
244 char *schema_entry = NULL;
247 /* We have been asked to skip some attributes/objectClasses */
248 if (attrs_skip && str_list_check_ci(attrs_skip, name)) {
253 /* We might have been asked to remap this oid, due to a conflict */
254 for (j=0; oid && oid_map && oid_map[j].old_oid; j++) {
255 if (strcasecmp(oid, oid_map[j].old_oid) == 0) {
256 oid = oid_map[j].new_oid;
264 /* We might have been asked to remap this oid,
265 * due to a conflict, or lack of
267 for (j=0; map.Standard_OID && oid_map && oid_map[j].old_oid; j++) {
268 if (strcasecmp(map.Standard_OID, oid_map[j].old_oid) == 0) {
269 map.Standard_OID = oid_map[j].new_oid;
277 /* We might have been asked to remap this name, due to a conflict */
278 for (j=0; name && attr_map && attr_map[j].old_attr; j++) {
279 if (strcasecmp(name, attr_map[j].old_attr) == 0) {
280 name = attr_map[j].new_attr;
286 case TARGET_OPENLDAP:
287 schema_entry = talloc_asprintf(mem_ctx,
290 case TARGET_FEDORA_DS:
291 schema_entry = talloc_asprintf(mem_ctx,
292 "attributeTypes: (");
295 IF_NULL_FAIL_RET(schema_entry);
297 schema_entry = talloc_asprintf_append(schema_entry,
298 "%s%s%s", seperator, oid, seperator);
300 schema_entry = talloc_asprintf_append(schema_entry,
301 "NAME '%s'%s", name, seperator);
302 IF_NULL_FAIL_RET(schema_entry);
305 #if 0 /* If you want to re-enable this, you must first figure out a sane escaping of ' in the description */
306 schema_entry = talloc_asprintf_append(schema_entry,
307 "DESC '%s' ", description);
308 IF_NULL_FAIL_RET(schema_entry);
313 if (map_p->equality) {
314 schema_entry = talloc_asprintf_append(schema_entry,
315 "EQUALITY %s%s", map_p->equality, seperator);
316 IF_NULL_FAIL_RET(schema_entry);
318 if (map_p->substring) {
319 schema_entry = talloc_asprintf_append(schema_entry,
320 "SUBSTR %s%s", map_p->substring, seperator);
321 IF_NULL_FAIL_RET(schema_entry);
324 syntax = map_p->Standard_OID;
327 schema_entry = talloc_asprintf_append(schema_entry,
328 "SYNTAX %s%s", syntax, seperator);
329 IF_NULL_FAIL_RET(schema_entry);
332 schema_entry = talloc_asprintf_append(schema_entry,
333 "SINGLE-VALUE%s", seperator);
334 IF_NULL_FAIL_RET(schema_entry);
337 schema_entry = talloc_asprintf_append(schema_entry,
341 case TARGET_OPENLDAP:
342 fprintf(out, "%s\n\n", schema_entry);
344 case TARGET_FEDORA_DS:
345 fprintf(out, "%s\n", schema_entry);
351 /* This is already sorted to have 'top' and similar classes first */
352 for (objectclass=schema->classes; objectclass; objectclass = objectclass->next) {
353 const char *name = objectclass->lDAPDisplayName;
354 const char *description = objectclass->adminDescription;
355 const char *oid = objectclass->governsID_oid;
356 const char *subClassOf = objectclass->subClassOf;
357 int objectClassCategory = objectclass->objectClassCategory;
360 char *schema_entry = NULL;
361 const char *objectclass_name_as_list[] = {
362 objectclass->lDAPDisplayName,
367 /* We have been asked to skip some attributes/objectClasses */
368 if (attrs_skip && str_list_check_ci(attrs_skip, name)) {
373 /* We might have been asked to remap this oid, due to a conflict */
374 for (j=0; oid_map && oid_map[j].old_oid; j++) {
375 if (strcasecmp(oid, oid_map[j].old_oid) == 0) {
376 oid = oid_map[j].new_oid;
381 /* We might have been asked to remap this name, due to a conflict */
382 for (j=0; name && attr_map && attr_map[j].old_attr; j++) {
383 if (strcasecmp(name, attr_map[j].old_attr) == 0) {
384 name = attr_map[j].new_attr;
389 may = dsdb_full_attribute_list(mem_ctx, schema, objectclass_name_as_list, DSDB_SCHEMA_ALL_MAY);
391 must = dsdb_full_attribute_list(mem_ctx, schema, objectclass_name_as_list, DSDB_SCHEMA_ALL_MUST);
394 case TARGET_OPENLDAP:
395 schema_entry = talloc_asprintf(mem_ctx,
398 case TARGET_FEDORA_DS:
399 schema_entry = talloc_asprintf(mem_ctx,
403 schema_entry = talloc_asprintf_append(schema_entry,
404 "%s%s%s", seperator, oid, seperator);
406 IF_NULL_FAIL_RET(schema_entry);
412 schema_entry = talloc_asprintf_append(schema_entry,
413 "NAME '%s'%s", name, seperator);
414 IF_NULL_FAIL_RET(schema_entry);
416 if (!schema_entry) return ret;
419 schema_entry = talloc_asprintf_append(schema_entry,
420 "DESC '%s'%s", description, seperator);
421 IF_NULL_FAIL_RET(schema_entry);
425 schema_entry = talloc_asprintf_append(schema_entry,
426 "SUP %s%s", subClassOf, seperator);
427 IF_NULL_FAIL_RET(schema_entry);
430 switch (objectClassCategory) {
432 schema_entry = talloc_asprintf_append(schema_entry,
433 "STRUCTURAL%s", seperator);
434 IF_NULL_FAIL_RET(schema_entry);
437 schema_entry = talloc_asprintf_append(schema_entry,
438 "ABSTRACT%s", seperator);
439 IF_NULL_FAIL_RET(schema_entry);
442 schema_entry = talloc_asprintf_append(schema_entry,
443 "AUXILIARY%s", seperator);
444 IF_NULL_FAIL_RET(schema_entry);
448 #define APPEND_ATTRS(attributes) \
451 for (k=0; attributes && attributes[k]; k++) { \
453 const char *attr_name = attributes[k]; \
454 /* We might have been asked to remap this name, due to a conflict */ \
455 for (attr_idx=0; attr_name && attr_map && attr_map[attr_idx].old_attr; attr_idx++) { \
456 if (strcasecmp(attr_name, attr_map[attr_idx].old_attr) == 0) { \
457 attr_name = attr_map[attr_idx].new_attr; \
462 schema_entry = talloc_asprintf_append(schema_entry, \
465 IF_NULL_FAIL_RET(schema_entry); \
466 if (attributes[k+1]) { \
467 IF_NULL_FAIL_RET(schema_entry); \
468 if (target == TARGET_OPENLDAP && ((k+1)%5 == 0)) { \
469 schema_entry = talloc_asprintf_append(schema_entry, \
470 "$%s ", seperator); \
471 IF_NULL_FAIL_RET(schema_entry); \
473 schema_entry = talloc_asprintf_append(schema_entry, \
481 schema_entry = talloc_asprintf_append(schema_entry,
483 IF_NULL_FAIL_RET(schema_entry);
487 schema_entry = talloc_asprintf_append(schema_entry,
489 IF_NULL_FAIL_RET(schema_entry);
493 schema_entry = talloc_asprintf_append(schema_entry,
495 IF_NULL_FAIL_RET(schema_entry);
499 schema_entry = talloc_asprintf_append(schema_entry,
501 IF_NULL_FAIL_RET(schema_entry);
504 schema_entry = talloc_asprintf_append(schema_entry,
508 case TARGET_OPENLDAP:
509 fprintf(out, "%s\n\n", schema_entry);
511 case TARGET_FEDORA_DS:
512 fprintf(out, "%s\n", schema_entry);
521 int main(int argc, const char **argv)
524 struct ldb_cmdline *options;
527 struct ldb_context *ldb;
528 struct schema_conv ret;
529 const char *target_str;
530 enum convert_target target;
532 ctx = talloc_new(NULL);
533 ldb = ldb_init(ctx, NULL);
535 options = ldb_cmdline_process(ldb, argc, argv, usage);
537 if (options->input) {
538 in = fopen(options->input, "r");
540 perror(options->input);
544 if (options->output) {
545 out = fopen(options->output, "w");
547 perror(options->output);
552 target_str = lp_parm_string(cmdline_lp_ctx, NULL, "convert", "target");
554 if (!target_str || strcasecmp(target_str, "openldap") == 0) {
555 target = TARGET_OPENLDAP;
556 } else if (strcasecmp(target_str, "fedora-ds") == 0) {
557 target = TARGET_FEDORA_DS;
559 printf("Unsupported target: %s\n", target_str);
563 ret = process_convert(ldb, target, in, out);
568 printf("Converted %d records (skipped %d) with %d failures\n", ret.count, ret.skipped, ret.failures);