/* ldb database library Copyright (C) Simo Sorce 2005 ** NOTE! The following LGPL license applies to the ldb ** library. This does NOT imply that all of Samba is released ** under the LGPL This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . */ /* * Name: ldb * * Component: oLschema2ldif * * Description: utility to convert an OpenLDAP schema into AD LDIF * * Author: Simo Sorce */ #include "includes.h" #include "ldb.h" #include "dsdb/samdb/samdb.h" #include "../lib/crypto/sha256.h" #include "../librpc/gen_ndr/ndr_misc.h" #include "lib/cmdline/popt_common.h" #define SCHEMA_UNKNOWN 0 #define SCHEMA_NAME 1 #define SCHEMA_SUP 2 #define SCHEMA_STRUCTURAL 3 #define SCHEMA_ABSTRACT 4 #define SCHEMA_AUXILIARY 5 #define SCHEMA_MUST 6 #define SCHEMA_MAY 7 #define SCHEMA_SINGLE_VALUE 8 #define SCHEMA_EQUALITY 9 #define SCHEMA_ORDERING 10 #define SCHEMA_SUBSTR 11 #define SCHEMA_SYNTAX 12 #define SCHEMA_DESC 13 struct schema_conv { int count; int failures; }; struct schema_token { int type; char *value; }; struct ldb_context *ldb_ctx; struct ldb_dn *basedn; static int check_braces(const char *string) { size_t b; char *c; b = 0; if ((c = strchr(string, '(')) == NULL) { return -1; } b++; c++; while (b) { c = strpbrk(c, "()"); if (c == NULL) return 1; if (*c == '(') b++; if (*c == ')') { b--; if (*(c - 1) != ' ' && c && (*(c + 1) == '\0')) { return 2; } } c++; } return 0; } static char *skip_spaces(char *string) { return (string + strspn(string, " \t\n")); } static int add_multi_string(struct ldb_message *msg, const char *attr, char *values) { char *c; char *s; int n; c = skip_spaces(values); while (*c) { n = strcspn(c, " \t$"); s = talloc_strndup(msg, c, n); if (ldb_msg_add_string(msg, attr, s) != 0) { return -1; } c += n; c += strspn(c, " \t$"); } return 0; } #define MSG_ADD_STRING(a, v) do { if (ldb_msg_add_string(msg, a, v) != 0) goto failed; } while(0) #define MSG_ADD_M_STRING(a, v) do { if (add_multi_string(msg, a, v) != 0) goto failed; } while(0) static char *get_def_value(TALLOC_CTX *ctx, char **string) { char *c = *string; char *value; int n; if (*c == '\'') { c++; n = strcspn(c, "\'"); value = talloc_strndup(ctx, c, n); c += n; c++; /* skip closing \' */ } else { n = strcspn(c, " \t\n"); value = talloc_strndup(ctx, c, n); c += n; } *string = c; return value; } static struct schema_token *get_next_schema_token(TALLOC_CTX *ctx, char **string) { char *c = skip_spaces(*string); char *type; struct schema_token *token; int n; token = talloc(ctx, struct schema_token); n = strcspn(c, " \t\n"); type = talloc_strndup(token, c, n); c += n; c = skip_spaces(c); if (strcasecmp("NAME", type) == 0) { talloc_free(type); token->type = SCHEMA_NAME; /* we do not support aliases so we get only the first name given and skip others */ if (*c == '(') { char *s = strchr(c, ')'); if (s == NULL) return NULL; s = skip_spaces(s); *string = s; c++; c = skip_spaces(c); } token->value = get_def_value(ctx, &c); if (*string < c) { /* single name */ c = skip_spaces(c); *string = c; } return token; } if (strcasecmp("SUP", type) == 0) { talloc_free(type); token->type = SCHEMA_SUP; if (*c == '(') { c++; n = strcspn(c, ")"); token->value = talloc_strndup(ctx, c, n); c += n; c++; } else { token->value = get_def_value(ctx, &c); } c = skip_spaces(c); *string = c; return token; } if (strcasecmp("STRUCTURAL", type) == 0) { talloc_free(type); token->type = SCHEMA_STRUCTURAL; *string = c; return token; } if (strcasecmp("ABSTRACT", type) == 0) { talloc_free(type); token->type = SCHEMA_ABSTRACT; *string = c; return token; } if (strcasecmp("AUXILIARY", type) == 0) { talloc_free(type); token->type = SCHEMA_AUXILIARY; *string = c; return token; } if (strcasecmp("MUST", type) == 0) { talloc_free(type); token->type = SCHEMA_MUST; if (*c == '(') { c++; n = strcspn(c, ")"); token->value = talloc_strndup(ctx, c, n); c += n; c++; } else { token->value = get_def_value(ctx, &c); } c = skip_spaces(c); *string = c; return token; } if (strcasecmp("MAY", type) == 0) { talloc_free(type); token->type = SCHEMA_MAY; if (*c == '(') { c++; n = strcspn(c, ")"); token->value = talloc_strndup(ctx, c, n); c += n; c++; } else { token->value = get_def_value(ctx, &c); } c = skip_spaces(c); *string = c; return token; } if (strcasecmp("SINGLE-VALUE", type) == 0) { talloc_free(type); token->type = SCHEMA_SINGLE_VALUE; *string = c; return token; } if (strcasecmp("EQUALITY", type) == 0) { talloc_free(type); token->type = SCHEMA_EQUALITY; token->value = get_def_value(ctx, &c); c = skip_spaces(c); *string = c; return token; } if (strcasecmp("ORDERING", type) == 0) { talloc_free(type); token->type = SCHEMA_ORDERING; token->value = get_def_value(ctx, &c); c = skip_spaces(c); *string = c; return token; } if (strcasecmp("SUBSTR", type) == 0) { talloc_free(type); token->type = SCHEMA_SUBSTR; token->value = get_def_value(ctx, &c); c = skip_spaces(c); *string = c; return token; } if (strcasecmp("SYNTAX", type) == 0) { talloc_free(type); token->type = SCHEMA_SYNTAX; token->value = get_def_value(ctx, &c); c = skip_spaces(c); *string = c; return token; } if (strcasecmp("DESC", type) == 0) { talloc_free(type); token->type = SCHEMA_DESC; token->value = get_def_value(ctx, &c); c = skip_spaces(c); *string = c; return token; } token->type = SCHEMA_UNKNOWN; token->value = type; if (*c == ')') { *string = c; return token; } if (*c == '\'') { c = strchr(++c, '\''); c++; } else { c += strcspn(c, " \t\n"); } c = skip_spaces(c); *string = c; return token; } static struct ldb_message *process_entry(TALLOC_CTX *mem_ctx, const char *entry) { TALLOC_CTX *ctx; struct ldb_message *msg; struct schema_token *token; char *c, *s; int n; SHA256_CTX sha256_context; uint8_t digest[SHA256_DIGEST_LENGTH]; struct GUID guid; bool isAttribute = false; bool single_valued = false; ctx = talloc_new(mem_ctx); if (ctx == NULL) { return NULL; } msg = ldb_msg_new(ctx); if (msg == NULL) { goto failed; } ldb_msg_add_string(msg, "objectClass", "top"); c = talloc_strdup(ctx, entry); if (!c) return NULL; c = skip_spaces(c); switch (*c) { case 'a': if (strncmp(c, "attributetype", 13) == 0) { c += 13; MSG_ADD_STRING("objectClass", "attributeSchema"); isAttribute = true; break; } goto failed; case 'o': if (strncmp(c, "objectclass", 11) == 0) { c += 11; MSG_ADD_STRING("objectClass", "classSchema"); break; } goto failed; default: goto failed; } c = strchr(c, '('); if (c == NULL) goto failed; c++; c = skip_spaces(c); /* get attributeID */ n = strcspn(c, " \t"); s = talloc_strndup(msg, c, n); if (isAttribute) { MSG_ADD_STRING("attributeID", s); } else { MSG_ADD_STRING("governsID", s); } samba_SHA256_Init(&sha256_context); samba_SHA256_Update(&sha256_context, (uint8_t*)s, strlen(s)); samba_SHA256_Final(digest, &sha256_context); memcpy(&guid, digest, sizeof(struct GUID)); if (dsdb_msg_add_guid(msg, &guid, "schemaIdGuid") != 0) { goto failed; } c += n; c = skip_spaces(c); while (*c != ')') { token = get_next_schema_token(msg, &c); if (!token) goto failed; switch (token->type) { case SCHEMA_NAME: MSG_ADD_STRING("cn", token->value); MSG_ADD_STRING("name", token->value); MSG_ADD_STRING("lDAPDisplayName", token->value); msg->dn = ldb_dn_copy(msg, basedn); ldb_dn_add_child_fmt(msg->dn, "CN=%s,CN=Schema,CN=Configuration", token->value); break; case SCHEMA_SUP: MSG_ADD_M_STRING("subClassOf", token->value); break; case SCHEMA_STRUCTURAL: MSG_ADD_STRING("objectClassCategory", "1"); break; case SCHEMA_ABSTRACT: MSG_ADD_STRING("objectClassCategory", "2"); break; case SCHEMA_AUXILIARY: MSG_ADD_STRING("objectClassCategory", "3"); break; case SCHEMA_MUST: MSG_ADD_M_STRING("mustContain", token->value); break; case SCHEMA_MAY: MSG_ADD_M_STRING("mayContain", token->value); break; case SCHEMA_SINGLE_VALUE: single_valued = true; break; case SCHEMA_EQUALITY: /* TODO */ break; case SCHEMA_ORDERING: /* TODO */ break; case SCHEMA_SUBSTR: /* TODO */ break; case SCHEMA_SYNTAX: { char *syntax_oid; const struct dsdb_syntax *map; char *oMSyntax; n = strcspn(token->value, "{"); syntax_oid = talloc_strndup(ctx, token->value, n); map = find_syntax_map_by_standard_oid(syntax_oid); if (!map) { break; } MSG_ADD_STRING("attributeSyntax", map->attributeSyntax_oid); oMSyntax = talloc_asprintf(msg, "%d", map->oMSyntax); MSG_ADD_STRING("oMSyntax", oMSyntax); break; } case SCHEMA_DESC: MSG_ADD_STRING("description", token->value); break; default: fprintf(stderr, "Unknown Definition: %s\n", token->value); } } if (isAttribute) { MSG_ADD_STRING("isSingleValued", single_valued ? "TRUE" : "FALSE"); } else { MSG_ADD_STRING("defaultObjectCategory", ldb_dn_get_linearized(msg->dn)); } talloc_steal(mem_ctx, msg); talloc_free(ctx); return msg; failed: talloc_free(ctx); return NULL; } static struct schema_conv process_file(FILE *in, FILE *out) { TALLOC_CTX *ctx; struct schema_conv ret; char *entry; int c, t, line; struct ldb_ldif ldif; ldif.changetype = LDB_CHANGETYPE_NONE; ctx = talloc_new(NULL); ret.count = 0; ret.failures = 0; line = 0; while ((c = fgetc(in)) != EOF) { line++; /* fprintf(stderr, "Parsing line %d\n", line); */ if (c == '#') { do { c = fgetc(in); } while (c != EOF && c != '\n'); continue; } if (c == '\n') { continue; } t = 0; entry = talloc_array(ctx, char, 1024); if (entry == NULL) exit(-1); do { if (c == '\n') { int ret2 = 0; entry[t] = '\0'; ret2 = check_braces(entry); if (ret2 == 0) { ret.count++; ldif.msg = process_entry(ctx, entry); if (ldif.msg == NULL) { ret.failures++; fprintf(stderr, "No valid msg from entry \n[%s]\n at line %d\n", entry, line); break; } ldb_ldif_write_file(ldb_ctx, out, &ldif); break; } if (ret2 == 2) { fprintf(stderr, "Invalid entry %s, closing braces need to be preceded by a space\n", entry); ret.failures++; break; } line++; } else { entry[t] = c; t++; } if ((t % 1023) == 0) { entry = talloc_realloc(ctx, entry, char, t + 1024); if (entry == NULL) exit(-1); } } while ((c = fgetc(in)) != EOF); if (c != '\n') { entry[t] = '\0'; if (check_braces(entry) == 0) { ret.count++; ldif.msg = process_entry(ctx, entry); if (ldif.msg == NULL) { ret.failures++; fprintf(stderr, "No valid msg from entry \n[%s]\n at line %d\n", entry, line); break; } ldb_ldif_write_file(ldb_ctx, out, &ldif); } else { fprintf(stderr, "malformed entry on line %d\n", line); ret.failures++; } } if (c == EOF) break; } return ret; } static struct options { const char *basedn; const char *input; const char *output; } options; static struct poptOption popt_options[] = { POPT_AUTOHELP { "basedn", 'b', POPT_ARG_STRING, &options.basedn, 0, "base DN", "DN" }, { "input", 'I', POPT_ARG_STRING, &options.input, 0, "inputfile of OpenLDAP style schema otherwise STDIN", "inputfile"}, { "output", 'O', POPT_ARG_STRING, &options.output, 0, "outputfile otherwise STDOUT", "outputfile"}, POPT_COMMON_VERSION { NULL } }; static void usage(void) { poptContext pc; printf("Usage: oLschema2ldif \n"); printf("\nConvert OpenLDAP schema to AD-like LDIF format\n\n"); printf("Converts records from an openLdap formatted schema to an ldif schema\n\n"); pc = poptGetContext("oLschema2ldif", 0, NULL, popt_options, POPT_CONTEXT_KEEP_FIRST); poptPrintHelp(pc, stdout, 0); exit(1); } int main(int argc, const char **argv) { TALLOC_CTX *ctx; struct schema_conv ret; FILE *in = stdin; FILE *out = stdout; poptContext pc; int opt; ctx = talloc_new(NULL); ldb_ctx = ldb_init(ctx, NULL); setenv("LDB_URL", "NONE", 1); pc = poptGetContext(argv[0], argc, argv, popt_options, POPT_CONTEXT_KEEP_FIRST); while((opt = poptGetNextOpt(pc)) != -1) { fprintf(stderr, "Invalid option %s: %s\n", poptBadOption(pc, 0), poptStrerror(opt)); usage(); } if (options.basedn == NULL) { printf("Base DN not specified\n"); usage(); exit(1); } else { basedn = ldb_dn_new(ctx, ldb_ctx, options.basedn); if ( ! ldb_dn_validate(basedn)) { printf("Malformed Base DN\n"); usage(); exit(1); } } if (options.input) { in = fopen(options.input, "r"); if (!in) { perror(options.input); usage(); exit(1); } } if (options.output) { out = fopen(options.output, "w"); if (!out) { perror(options.output); usage(); exit(1); } } ret = process_file(in, out); fclose(in); fclose(out); printf("Converted %d records with %d failures\n", ret.count, ret.failures); return 0; }