1/* 2 ldb database library 3 4 Copyright (C) Andrew Bartlett 2006 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: ad2oLschema 28 * 29 * Description: utility to convert an AD schema into the format required by OpenLDAP 30 * 31 * Author: Andrew Tridgell 32 */ 33 34#include "includes.h" 35#include "ldb/include/includes.h" 36#include "system/locale.h" 37#include "ldb/tools/cmdline.h" 38#include "ldb/tools/convert.h" 39 40struct schema_conv { 41 int count; 42 int skipped; 43 int failures; 44}; 45 46enum convert_target { 47 TARGET_OPENLDAP, 48 TARGET_FEDORA_DS 49}; 50 51 52static void usage(void) 53{ 54 printf("Usage: ad2oLschema <options>\n"); 55 printf("\nConvert AD-like LDIF to OpenLDAP schema format\n\n"); 56 printf("Options:\n"); 57 printf(" -I inputfile inputfile of mapped OIDs and skipped attributes/ObjectClasses"); 58 printf(" -H url LDB or LDAP server to read schmea from\n"); 59 printf(" -O outputfile outputfile otherwise STDOUT\n"); 60 printf(" -o options pass options like modules to activate\n"); 61 printf(" e.g: -o modules:timestamps\n"); 62 printf("\n"); 63 printf("Converts records from an AD-like LDIF schema into an openLdap formatted schema\n\n"); 64 exit(1); 65} 66 67static int fetch_attrs_schema(struct ldb_context *ldb, struct ldb_dn *schemadn, 68 TALLOC_CTX *mem_ctx, 69 struct ldb_result **attrs_res) 70{ 71 TALLOC_CTX *local_ctx = talloc_new(mem_ctx); 72 int ret; 73 const char *attrs[] = { 74 "lDAPDisplayName", 75 "isSingleValued", 76 "attributeID", 77 "attributeSyntax", 78 "description", 79 NULL 80 }; 81 82 if (!local_ctx) { 83 return LDB_ERR_OPERATIONS_ERROR; 84 } 85 86 /* Downlaod schema */ 87 ret = ldb_search(ldb, ldb, attrs_res, schemadn, LDB_SCOPE_SUBTREE, 88 attrs, "objectClass=attributeSchema"); 89 if (ret != LDB_SUCCESS) { 90 printf("Search failed: %s\n", ldb_errstring(ldb)); 91 return LDB_ERR_OPERATIONS_ERROR; 92 } 93 94 return ret; 95} 96 97static const char *oc_attrs[] = { 98 "lDAPDisplayName", 99 "mayContain", 100 "mustContain", 101 "systemMayContain", 102 "systemMustContain", 103 "objectClassCategory", 104 "governsID", 105 "description", 106 "subClassOf", 107 NULL 108}; 109 110static int fetch_oc_recursive(struct ldb_context *ldb, struct ldb_dn *schemadn, 111 TALLOC_CTX *mem_ctx, 112 struct ldb_result *search_from, 113 struct ldb_result *res_list) 114{ 115 int i; 116 int ret = 0; 117 for (i=0; i < search_from->count; i++) { 118 struct ldb_result *res; 119 const char *name = ldb_msg_find_attr_as_string(search_from->msgs[i], 120 "lDAPDisplayname", NULL); 121 122 ret = ldb_search(ldb, ldb, &res, schemadn, LDB_SCOPE_SUBTREE, 123 oc_attrs, "(&(&(objectClass=classSchema)(subClassOf=%s))(!(lDAPDisplayName=%s)))", 124 name, name); 125 if (ret != LDB_SUCCESS) { 126 printf("Search failed: %s\n", ldb_errstring(ldb)); 127 return ret; 128 } 129 130 talloc_steal(mem_ctx, res); 131 132 res_list->msgs = talloc_realloc(res_list, res_list->msgs, 133 struct ldb_message *, res_list->count + 2); 134 if (!res_list->msgs) { 135 return LDB_ERR_OPERATIONS_ERROR; 136 } 137 res_list->msgs[res_list->count] = talloc_move(res_list, 138 &search_from->msgs[i]); 139 res_list->count++; 140 res_list->msgs[res_list->count] = NULL; 141 142 if (res->count > 0) { 143 ret = fetch_oc_recursive(ldb, schemadn, mem_ctx, res, res_list); 144 } 145 if (ret != LDB_SUCCESS) { 146 return ret; 147 } 148 } 149 return ret; 150} 151 152static int fetch_objectclass_schema(struct ldb_context *ldb, struct ldb_dn *schemadn, 153 TALLOC_CTX *mem_ctx, 154 struct ldb_result **objectclasses_res) 155{ 156 TALLOC_CTX *local_ctx = talloc_new(mem_ctx); 157 struct ldb_result *top_res, *ret_res; 158 int ret; 159 if (!local_ctx) { 160 return LDB_ERR_OPERATIONS_ERROR; 161 } 162 163 /* Downlaod 'top' */ 164 ret = ldb_search(ldb, ldb, &top_res, schemadn, LDB_SCOPE_SUBTREE, 165 oc_attrs, "(&(objectClass=classSchema)(lDAPDisplayName=top))"); 166 if (ret != LDB_SUCCESS) { 167 printf("Search failed: %s\n", ldb_errstring(ldb)); 168 return LDB_ERR_OPERATIONS_ERROR; 169 } 170 171 talloc_steal(local_ctx, top_res); 172 173 if (top_res->count != 1) { 174 return LDB_ERR_OPERATIONS_ERROR; 175 } 176 177 ret_res = talloc_zero(local_ctx, struct ldb_result); 178 if (!ret_res) { 179 return LDB_ERR_OPERATIONS_ERROR; 180 } 181 182 ret = fetch_oc_recursive(ldb, schemadn, local_ctx, top_res, ret_res); 183 184 if (ret != LDB_SUCCESS) { 185 printf("Search failed: %s\n", ldb_errstring(ldb)); 186 return LDB_ERR_OPERATIONS_ERROR; 187 } 188 189 *objectclasses_res = talloc_move(mem_ctx, &ret_res); 190 return ret; 191} 192 193static struct ldb_dn *find_schema_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx) 194{ 195 const char *rootdse_attrs[] = {"schemaNamingContext", NULL}; 196 struct ldb_dn *schemadn; 197 struct ldb_dn *basedn = ldb_dn_explode(mem_ctx, ""); 198 struct ldb_result *rootdse_res; 199 int ldb_ret; 200 if (!basedn) { 201 return NULL; 202 } 203 204 /* Search for rootdse */ 205 ldb_ret = ldb_search(ldb, ldb, &rootdse_res, basedn, LDB_SCOPE_BASE, rootdse_attrs, NULL); 206 if (ldb_ret != LDB_SUCCESS) { 207 printf("Search failed: %s\n", ldb_errstring(ldb)); 208 return NULL; 209 } 210 211 talloc_steal(mem_ctx, rootdse_res); 212 213 if (rootdse_res->count != 1) { 214 printf("Failed to find rootDSE"); 215 return NULL; 216 } 217 218 /* Locate schema */ 219 schemadn = ldb_msg_find_attr_as_dn(mem_ctx, rootdse_res->msgs[0], "schemaNamingContext"); 220 if (!schemadn) { 221 return NULL; 222 } 223 224 talloc_free(rootdse_res); 225 return schemadn; 226} 227 228#define IF_NULL_FAIL_RET(x) do { \ 229 if (!x) { \ 230 ret.failures++; \ 231 return ret; \ 232 } \ 233 } while (0) 234 235 236static struct schema_conv process_convert(struct ldb_context *ldb, enum convert_target target, FILE *in, FILE *out) 237{ 238 /* Read list of attributes to skip, OIDs to map */ 239 TALLOC_CTX *mem_ctx = talloc_new(ldb); 240 char *line; 241 const char **attrs_skip = NULL; 242 int num_skip = 0; 243 struct oid_map { 244 char *old_oid; 245 char *new_oid; 246 } *oid_map = NULL; 247 int num_maps = 0; 248 struct ldb_result *attrs_res, *objectclasses_res; 249 struct ldb_dn *schemadn; 250 struct schema_conv ret; 251 252 int ldb_ret, i; 253 254 ret.count = 0; 255 ret.skipped = 0; 256 ret.failures = 0; 257 258 while ((line = afdgets(fileno(in), mem_ctx, 0))) { 259 /* Blank Line */ 260 if (line[0] == '\0') { 261 continue; 262 } 263 /* Comment */ 264 if (line[0] == '#') { 265 continue; 266 } 267 if (isdigit(line[0])) { 268 char *p = strchr(line, ':'); 269 IF_NULL_FAIL_RET(p); 270 if (!p) { 271 ret.failures = 1; 272 return ret; 273 } 274 p[0] = '\0'; 275 p++; 276 oid_map = talloc_realloc(mem_ctx, oid_map, struct oid_map, num_maps + 2); 277 trim_string(line, " ", " "); 278 oid_map[num_maps].old_oid = talloc_move(oid_map, &line); 279 trim_string(p, " ", " "); 280 oid_map[num_maps].new_oid = p; 281 num_maps++; 282 oid_map[num_maps].old_oid = NULL; 283 } else { 284 attrs_skip = talloc_realloc(mem_ctx, attrs_skip, const char *, num_skip + 2); 285 trim_string(line, " ", " "); 286 attrs_skip[num_skip] = talloc_move(attrs_skip, &line); 287 num_skip++; 288 attrs_skip[num_skip] = NULL; 289 } 290 } 291 292 schemadn = find_schema_dn(ldb, mem_ctx); 293 if (!schemadn) { 294 printf("Failed to find schema DN: %s\n", ldb_errstring(ldb)); 295 ret.failures = 1; 296 return ret; 297 } 298 299 ldb_ret = fetch_attrs_schema(ldb, schemadn, mem_ctx, &attrs_res); 300 if (ldb_ret != LDB_SUCCESS) { 301 printf("Failed to fetch attribute schema: %s\n", ldb_errstring(ldb)); 302 ret.failures = 1; 303 return ret; 304 } 305 306 switch (target) { 307 case TARGET_OPENLDAP: 308 break; 309 case TARGET_FEDORA_DS: 310 fprintf(out, "dn: cn=schema\n"); 311 break; 312 } 313 314 for (i=0; i < attrs_res->count; i++) { 315 struct ldb_message *msg = attrs_res->msgs[i]; 316 317 const char *name = ldb_msg_find_attr_as_string(msg, "lDAPDisplayName", NULL); 318 const char *description = ldb_msg_find_attr_as_string(msg, "description", NULL); 319 const char *oid = ldb_msg_find_attr_as_string(msg, "attributeID", NULL); 320 const char *syntax = ldb_msg_find_attr_as_string(msg, "attributeSyntax", NULL); 321 BOOL single_value = ldb_msg_find_attr_as_bool(msg, "isSingleValued", False); 322 const struct syntax_map *map = find_syntax_map_by_ad_oid(syntax); 323 char *schema_entry = NULL; 324 int j; 325 326 /* We have been asked to skip some attributes/objectClasses */ 327 if (attrs_skip && str_list_check_ci(attrs_skip, name)) { 328 ret.skipped++; 329 continue; 330 } 331 332 /* We might have been asked to remap this oid, due to a conflict */ 333 for (j=0; oid && oid_map[j].old_oid; j++) { 334 if (strcmp(oid, oid_map[j].old_oid) == 0) { 335 oid = oid_map[j].new_oid; 336 break; 337 } 338 } 339 340 switch (target) { 341 case TARGET_OPENLDAP: 342 schema_entry = talloc_asprintf(mem_ctx, 343 "attributetype (\n" 344 " %s\n", oid); 345 break; 346 case TARGET_FEDORA_DS: 347 schema_entry = talloc_asprintf(mem_ctx, 348 "attributeTypes: (\n" 349 " %s\n", oid); 350 break; 351 } 352 IF_NULL_FAIL_RET(schema_entry); 353 354 schema_entry = talloc_asprintf_append(schema_entry, 355 " NAME '%s'\n", name); 356 IF_NULL_FAIL_RET(schema_entry); 357 358 if (description) { 359 schema_entry = talloc_asprintf_append(schema_entry, 360 " DESC %s\n", description); 361 IF_NULL_FAIL_RET(schema_entry); 362 } 363 364 if (map) { 365 const char *syntax_oid; 366 if (map->equality) { 367 schema_entry = talloc_asprintf_append(schema_entry, 368 " EQUALITY %s\n", map->equality); 369 IF_NULL_FAIL_RET(schema_entry); 370 } 371 if (map->substring) { 372 schema_entry = talloc_asprintf_append(schema_entry, 373 " SUBSTR %s\n", map->substring); 374 IF_NULL_FAIL_RET(schema_entry); 375 } 376 syntax_oid = map->Standard_OID; 377 /* We might have been asked to remap this oid, 378 * due to a conflict, or lack of 379 * implementation */ 380 for (j=0; syntax_oid && oid_map[j].old_oid; j++) { 381 if (strcmp(syntax_oid, oid_map[j].old_oid) == 0) { 382 syntax_oid = oid_map[j].new_oid; 383 break; 384 } 385 } 386 schema_entry = talloc_asprintf_append(schema_entry, 387 " SYNTAX %s\n", syntax_oid); 388 IF_NULL_FAIL_RET(schema_entry); 389 } 390 391 if (single_value) { 392 schema_entry = talloc_asprintf_append(schema_entry, 393 " SINGLE-VALUE\n"); 394 IF_NULL_FAIL_RET(schema_entry); 395 } 396 397 schema_entry = talloc_asprintf_append(schema_entry, 398 " )"); 399 400 switch (target) { 401 case TARGET_OPENLDAP: 402 fprintf(out, "%s\n\n", schema_entry); 403 break; 404 case TARGET_FEDORA_DS: 405 fprintf(out, "%s\n", schema_entry); 406 break; 407 } 408 ret.count++; 409 } 410 411 ldb_ret = fetch_objectclass_schema(ldb, schemadn, mem_ctx, &objectclasses_res); 412 if (ldb_ret != LDB_SUCCESS) { 413 printf("Failed to fetch objectClass schema elements: %s\n", ldb_errstring(ldb)); 414 ret.failures = 1; 415 return ret; 416 } 417 418 for (i=0; i < objectclasses_res->count; i++) { 419 struct ldb_message *msg = objectclasses_res->msgs[i]; 420 const char *name = ldb_msg_find_attr_as_string(msg, "lDAPDisplayName", NULL); 421 const char *description = ldb_msg_find_attr_as_string(msg, "description", NULL); 422 const char *oid = ldb_msg_find_attr_as_string(msg, "governsID", NULL); 423 const char *subClassOf = ldb_msg_find_attr_as_string(msg, "subClassOf", NULL); 424 int objectClassCategory = ldb_msg_find_attr_as_int(msg, "objectClassCategory", 0); 425 struct ldb_message_element *must = ldb_msg_find_element(msg, "mustContain"); 426 struct ldb_message_element *sys_must = ldb_msg_find_element(msg, "systemMustContain"); 427 struct ldb_message_element *may = ldb_msg_find_element(msg, "mayContain"); 428 struct ldb_message_element *sys_may = ldb_msg_find_element(msg, "systemMayContain"); 429 char *schema_entry = NULL; 430 int j; 431 432 /* We have been asked to skip some attributes/objectClasses */ 433 if (attrs_skip && str_list_check_ci(attrs_skip, name)) { 434 ret.skipped++; 435 continue; 436 } 437 438 /* We might have been asked to remap this oid, due to a conflict */ 439 for (j=0; oid_map[j].old_oid; j++) { 440 if (strcmp(oid, oid_map[j].old_oid) == 0) { 441 oid = oid_map[j].new_oid; 442 break; 443 } 444 } 445 446 switch (target) { 447 case TARGET_OPENLDAP: 448 schema_entry = talloc_asprintf(mem_ctx, 449 "objectclass (\n" 450 " %s\n", oid); 451 break; 452 case TARGET_FEDORA_DS: 453 schema_entry = talloc_asprintf(mem_ctx, 454 "objectClasses: (\n" 455 " %s\n", oid); 456 break; 457 } 458 IF_NULL_FAIL_RET(schema_entry); 459 if (!schema_entry) { 460 ret.failures++; 461 break; 462 } 463 464 schema_entry = talloc_asprintf_append(schema_entry, 465 " NAME '%s'\n", name); 466 IF_NULL_FAIL_RET(schema_entry); 467 468 if (!schema_entry) return ret; 469 470 if (description) { 471 schema_entry = talloc_asprintf_append(schema_entry, 472 " DESC %s\n", description); 473 IF_NULL_FAIL_RET(schema_entry); 474 } 475 476 if (subClassOf) { 477 schema_entry = talloc_asprintf_append(schema_entry, 478 " SUP %s\n", subClassOf); 479 IF_NULL_FAIL_RET(schema_entry); 480 } 481 482 switch (objectClassCategory) { 483 case 1: 484 schema_entry = talloc_asprintf_append(schema_entry, 485 " STRUCTURAL\n"); 486 IF_NULL_FAIL_RET(schema_entry); 487 break; 488 case 2: 489 schema_entry = talloc_asprintf_append(schema_entry, 490 " ABSTRACT\n"); 491 IF_NULL_FAIL_RET(schema_entry); 492 break; 493 case 3: 494 schema_entry = talloc_asprintf_append(schema_entry, 495 " AUXILIARY\n"); 496 IF_NULL_FAIL_RET(schema_entry); 497 break; 498 } 499 500#define APPEND_ATTRS(attributes) \ 501 do { \ 502 int k; \ 503 for (k=0; attributes && k < attributes->num_values; k++) { \ 504 schema_entry = talloc_asprintf_append(schema_entry, \ 505 " %s", \ 506 (const char *)attributes->values[k].data); \ 507 IF_NULL_FAIL_RET(schema_entry); \ 508 if (k != (attributes->num_values - 1)) { \ 509 schema_entry = talloc_asprintf_append(schema_entry, \ 510 " $"); \ 511 IF_NULL_FAIL_RET(schema_entry); \ 512 if (target == TARGET_OPENLDAP && ((k+1)%5 == 0)) { \ 513 schema_entry = talloc_asprintf_append(schema_entry, \ 514 "\n "); \ 515 IF_NULL_FAIL_RET(schema_entry); \ 516 } \ 517 } \ 518 } \ 519 } while (0) 520 521 if (must || sys_must) { 522 schema_entry = talloc_asprintf_append(schema_entry, 523 " MUST ("); 524 IF_NULL_FAIL_RET(schema_entry); 525 526 APPEND_ATTRS(must); 527 if (must && sys_must) { 528 schema_entry = talloc_asprintf_append(schema_entry, \ 529 " $"); \ 530 } 531 APPEND_ATTRS(sys_must); 532 533 schema_entry = talloc_asprintf_append(schema_entry, 534 " )\n"); 535 IF_NULL_FAIL_RET(schema_entry); 536 } 537 538 if (may || sys_may) { 539 schema_entry = talloc_asprintf_append(schema_entry, 540 " MAY ("); 541 IF_NULL_FAIL_RET(schema_entry); 542 543 APPEND_ATTRS(may); 544 if (may && sys_may) { 545 schema_entry = talloc_asprintf_append(schema_entry, \ 546 " $"); \ 547 } 548 APPEND_ATTRS(sys_may); 549 550 schema_entry = talloc_asprintf_append(schema_entry, 551 " )\n"); 552 IF_NULL_FAIL_RET(schema_entry); 553 } 554 555 schema_entry = talloc_asprintf_append(schema_entry, 556 " )"); 557 558 switch (target) { 559 case TARGET_OPENLDAP: 560 fprintf(out, "%s\n\n", schema_entry); 561 break; 562 case TARGET_FEDORA_DS: 563 fprintf(out, "%s\n", schema_entry); 564 break; 565 } 566 ret.count++; 567 } 568 569 return ret; 570} 571 572 int main(int argc, const char **argv) 573{ 574 TALLOC_CTX *ctx; 575 struct ldb_cmdline *options; 576 FILE *in = stdin; 577 FILE *out = stdout; 578 struct ldb_context *ldb; 579 struct schema_conv ret; 580 const char *target_str; 581 enum convert_target target; 582 583 ldb_global_init(); 584 585 ctx = talloc_new(NULL); 586 ldb = ldb_init(ctx); 587 588 options = ldb_cmdline_process(ldb, argc, argv, usage); 589 590 if (options->input) { 591 in = fopen(options->input, "r"); 592 if (!in) { 593 perror(options->input); 594 exit(1); 595 } 596 } 597 if (options->output) { 598 out = fopen(options->output, "w"); 599 if (!out) { 600 perror(options->output); 601 exit(1); 602 } 603 } 604 605 target_str = lp_parm_string(-1, "convert", "target"); 606 607 if (!target_str || strcasecmp(target_str, "openldap") == 0) { 608 target = TARGET_OPENLDAP; 609 } else if (strcasecmp(target_str, "fedora-ds") == 0) { 610 target = TARGET_FEDORA_DS; 611 } else { 612 printf("Unsupported target: %s\n", target_str); 613 exit(1); 614 } 615 616 ret = process_convert(ldb, target, in, out); 617 618 fclose(in); 619 fclose(out); 620 621 printf("Converted %d records (skipped %d) with %d failures\n", ret.count, ret.skipped, ret.failures); 622 623 return 0; 624} 625