Make ad2oLschema even simpler, by moving the heavy work into dsdb.
[tprouty/samba.git] / source4 / utils / ad2oLschema.c
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 Bartlett
32  */
33
34 #include "includes.h"
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"
42
43 struct schema_conv {
44         int count;
45         int skipped;
46         int failures;
47 };
48
49 enum convert_target {
50         TARGET_OPENLDAP,
51         TARGET_FEDORA_DS
52 };
53         
54
55 static void usage(void)
56 {
57         printf("Usage: ad2oLschema <options>\n");
58         printf("\nConvert AD-like LDIF to OpenLDAP schema format\n\n");
59         printf("Options:\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");
65         printf("\n");
66         printf("Converts records from an AD-like LDIF schema into an openLdap formatted schema\n\n");
67         exit(1);
68 }
69
70 static struct ldb_dn *find_schema_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx) 
71 {
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;
77         int ldb_ret;
78         
79         if (!basedn) {
80                 return NULL;
81         }
82         
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))", 
88                                  NULL, &schema_res);
89                 if (ldb_ret) {
90                         printf("cn=Schema Search failed: %s\n", ldb_errstring(ldb));
91                         return NULL;
92                 }
93
94                 talloc_steal(mem_ctx, schema_res);
95
96                 if (schema_res->count != 1) {
97                         talloc_free(schema_res);
98                         printf("Failed to find rootDSE");
99                         return NULL;
100                 }
101                 
102                 schemadn = talloc_steal(mem_ctx, schema_res->msgs[0]->dn);
103                 talloc_free(schema_res);
104                 return schemadn;
105         }
106         
107         if (rootdse_res->count != 1) {
108                 printf("Failed to find rootDSE");
109                 talloc_free(rootdse_res);
110                 return NULL;
111         }
112         
113         /* Locate schema */
114         schemadn = ldb_msg_find_attr_as_dn(ldb, mem_ctx, rootdse_res->msgs[0], "schemaNamingContext");
115         talloc_free(rootdse_res);
116
117         if (!schemadn) {
118                 return NULL;
119         }
120
121         return schemadn;
122 }
123
124
125 #define IF_NULL_FAIL_RET(x) do {     \
126                 if (!x) {               \
127                         ret.failures++; \
128                         return ret;     \
129                 }                       \
130         } while (0) 
131
132
133 static struct schema_conv process_convert(struct ldb_context *ldb, enum convert_target target, FILE *in, FILE *out) 
134 {
135         /* Read list of attributes to skip, OIDs to map */
136         TALLOC_CTX *mem_ctx = talloc_new(ldb);
137         char *line;
138         const char **attrs_skip = NULL;
139         int num_skip = 0;
140         struct oid_map {
141                 char *old_oid;
142                 char *new_oid;
143         } *oid_map = NULL;
144         int num_oid_maps = 0;
145         struct attr_map {
146                 char *old_attr;
147                 char *new_attr;
148         } *attr_map = NULL;
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         char *error_string;
156
157         int ldb_ret;
158
159         ret.count = 0;
160         ret.skipped = 0;
161         ret.failures = 0;
162
163         while ((line = afdgets(fileno(in), mem_ctx, 0))) {
164                 /* Blank Line */
165                 if (line[0] == '\0') {
166                         continue;
167                 }
168                 /* Comment */
169                 if (line[0] == '#') {
170                         continue;
171                 }
172                 if (isdigit(line[0])) {
173                         char *p = strchr(line, ':');
174                         IF_NULL_FAIL_RET(p);
175                         p[0] = '\0';
176                         p++;
177                         oid_map = talloc_realloc(mem_ctx, oid_map, struct oid_map, num_oid_maps + 2);
178                         trim_string(line, " ", " ");
179                         oid_map[num_oid_maps].old_oid = talloc_move(oid_map, &line);
180                         trim_string(p, " ", " ");
181                         oid_map[num_oid_maps].new_oid = p;
182                         num_oid_maps++;
183                         oid_map[num_oid_maps].old_oid = NULL;
184                 } else {
185                         char *p = strchr(line, ':');
186                         if (p) {
187                                 /* remap attribute/objectClass */
188                                 p[0] = '\0';
189                                 p++;
190                                 attr_map = talloc_realloc(mem_ctx, attr_map, struct attr_map, num_attr_maps + 2);
191                                 trim_string(line, " ", " ");
192                                 attr_map[num_attr_maps].old_attr = talloc_move(attr_map, &line);
193                                 trim_string(p, " ", " ");
194                                 attr_map[num_attr_maps].new_attr = p;
195                                 num_attr_maps++;
196                                 attr_map[num_attr_maps].old_attr = NULL;
197                         } else {
198                                 /* skip attribute/objectClass */
199                                 attrs_skip = talloc_realloc(mem_ctx, attrs_skip, const char *, num_skip + 2);
200                                 trim_string(line, " ", " ");
201                                 attrs_skip[num_skip] = talloc_move(attrs_skip, &line);
202                                 num_skip++;
203                                 attrs_skip[num_skip] = NULL;
204                         }
205                 }
206         }
207
208         schemadn = find_schema_dn(ldb, mem_ctx);
209         if (!schemadn) {
210                 printf("Failed to find schema DN: %s\n", ldb_errstring(ldb));
211                 ret.failures = 1;
212                 return ret;
213         }
214         
215         ldb_ret = dsdb_schema_from_schema_dn(mem_ctx, ldb,
216                                              lp_iconv_convenience(cmdline_lp_ctx),
217                                              schemadn, &schema, &error_string);
218         if (ldb_ret != LDB_SUCCESS) {
219                 printf("Failed to load schema: %s\n", error_string);
220                 ret.failures = 1;
221                 return ret;
222         }
223
224         switch (target) {
225         case TARGET_OPENLDAP:
226                 break;
227         case TARGET_FEDORA_DS:
228                 fprintf(out, "dn: cn=schema\n");
229                 break;
230         }
231
232         for (attribute=schema->attributes; attribute; attribute = attribute->next) {
233                 const char *name = attribute->lDAPDisplayName;
234                 const char *description = attribute->adminDescription;
235                 const char *oid = attribute->attributeID_oid;
236                 const char *syntax = attribute->attributeSyntax_oid;
237                 bool single_value = attribute->isSingleValued;
238
239                 const struct syntax_map *map = find_syntax_map_by_ad_oid(syntax);
240                 char *schema_entry = NULL;
241                 int j;
242
243                 /* We have been asked to skip some attributes/objectClasses */
244                 if (attrs_skip && str_list_check_ci(attrs_skip, name)) {
245                         ret.skipped++;
246                         continue;
247                 }
248
249                 /* We might have been asked to remap this oid, due to a conflict */
250                 for (j=0; oid && oid_map && oid_map[j].old_oid; j++) {
251                         if (strcasecmp(oid, oid_map[j].old_oid) == 0) {
252                                 oid =  oid_map[j].new_oid;
253                                 break;
254                         }
255                 }
256                 
257                 switch (target) {
258                 case TARGET_OPENLDAP:
259                         schema_entry = talloc_asprintf(mem_ctx, 
260                                                        "attributetype (\n"
261                                                        "  %s\n", oid);
262                         break;
263                 case TARGET_FEDORA_DS:
264                         schema_entry = talloc_asprintf(mem_ctx, 
265                                                        "attributeTypes: (\n"
266                                                        "  %s\n", oid);
267                         break;
268                 }
269                 IF_NULL_FAIL_RET(schema_entry);
270
271                 /* We might have been asked to remap this name, due to a conflict */
272                 for (j=0; name && attr_map && attr_map[j].old_attr; j++) {
273                         if (strcasecmp(name, attr_map[j].old_attr) == 0) {
274                                 name =  attr_map[j].new_attr;
275                                 break;
276                         }
277                 }
278                 
279                 schema_entry = talloc_asprintf_append(schema_entry, 
280                                                       "  NAME '%s'\n", name);
281                 IF_NULL_FAIL_RET(schema_entry);
282
283                 if (description) {
284 #if 0 /* If you want to re-enable this, you must first figure out a sane escaping of ' in the description */
285                         schema_entry = talloc_asprintf_append(schema_entry, 
286                                                               "  DESC '%s'\n", description);
287                         IF_NULL_FAIL_RET(schema_entry);
288 #endif
289                 }
290
291                 if (map) {
292                         const char *syntax_oid;
293                         if (map->equality) {
294                                 schema_entry = talloc_asprintf_append(schema_entry, 
295                                                                       "  EQUALITY %s\n", map->equality);
296                                 IF_NULL_FAIL_RET(schema_entry);
297                         }
298                         if (map->substring) {
299                                 schema_entry = talloc_asprintf_append(schema_entry, 
300                                                                       "  SUBSTR %s\n", map->substring);
301                                 IF_NULL_FAIL_RET(schema_entry);
302                         }
303                         syntax_oid = map->Standard_OID;
304                         /* We might have been asked to remap this oid,
305                          * due to a conflict, or lack of
306                          * implementation */
307                         for (j=0; syntax_oid && oid_map && oid_map[j].old_oid; j++) {
308                                 if (strcasecmp(syntax_oid, oid_map[j].old_oid) == 0) {
309                                         syntax_oid =  oid_map[j].new_oid;
310                                         break;
311                                 }
312                         }
313                         schema_entry = talloc_asprintf_append(schema_entry, 
314                                                               "  SYNTAX %s\n", syntax_oid);
315                         IF_NULL_FAIL_RET(schema_entry);
316                 }
317
318                 if (single_value) {
319                         schema_entry = talloc_asprintf_append(schema_entry, 
320                                                               "  SINGLE-VALUE\n");
321                         IF_NULL_FAIL_RET(schema_entry);
322                 }
323                 
324                 schema_entry = talloc_asprintf_append(schema_entry, 
325                                                       "  )");
326
327                 switch (target) {
328                 case TARGET_OPENLDAP:
329                         fprintf(out, "%s\n\n", schema_entry);
330                         break;
331                 case TARGET_FEDORA_DS:
332                         fprintf(out, "%s\n", schema_entry);
333                         break;
334                 }
335                 ret.count++;
336         }
337
338         /* This is already sorted to have 'top' and similar classes first */
339         for (objectclass=schema->classes; objectclass; objectclass = objectclass->next) {
340                 const char *name = objectclass->lDAPDisplayName;
341                 const char *description = objectclass->adminDescription;
342                 const char *oid = objectclass->governsID_oid;
343                 const char *subClassOf = objectclass->subClassOf;
344                 int objectClassCategory = objectclass->objectClassCategory;
345                 char **must;
346                 char **may;
347                 char *schema_entry = NULL;
348                 const char *objectclass_name_as_list[] = {
349                         objectclass->lDAPDisplayName,
350                         NULL
351                 };
352                 int j;
353                 
354                 /* We have been asked to skip some attributes/objectClasses */
355                 if (attrs_skip && str_list_check_ci(attrs_skip, name)) {
356                         ret.skipped++;
357                         continue;
358                 }
359
360                 may = dsdb_full_attribute_list(mem_ctx, schema, objectclass_name_as_list, DSDB_SCHEMA_ALL_MAY);
361
362                 must = dsdb_full_attribute_list(mem_ctx, schema, objectclass_name_as_list, DSDB_SCHEMA_ALL_MUST);
363
364                 /* We might have been asked to remap this oid, due to a conflict */
365                 for (j=0; oid_map && oid_map[j].old_oid; j++) {
366                         if (strcasecmp(oid, oid_map[j].old_oid) == 0) {
367                                 oid =  oid_map[j].new_oid;
368                                 break;
369                         }
370                 }
371                 
372                 switch (target) {
373                 case TARGET_OPENLDAP:
374                         schema_entry = talloc_asprintf(mem_ctx, 
375                                                        "objectclass (\n"
376                                                        "  %s\n", oid);
377                         break;
378                 case TARGET_FEDORA_DS:
379                         schema_entry = talloc_asprintf(mem_ctx, 
380                                                        "objectClasses: (\n"
381                                                        "  %s\n", oid);
382                         break;
383                 }
384                 IF_NULL_FAIL_RET(schema_entry);
385                 if (!schema_entry) {
386                         ret.failures++;
387                         break;
388                 }
389
390                 /* We might have been asked to remap this name, due to a conflict */
391                 for (j=0; name && attr_map && attr_map[j].old_attr; j++) {
392                         if (strcasecmp(name, attr_map[j].old_attr) == 0) {
393                                 name =  attr_map[j].new_attr;
394                                 break;
395                         }
396                 }
397                 
398                 schema_entry = talloc_asprintf_append(schema_entry, 
399                                                       "  NAME '%s'\n", name);
400                 IF_NULL_FAIL_RET(schema_entry);
401
402                 if (!schema_entry) return ret;
403
404                 if (description) {
405                         schema_entry = talloc_asprintf_append(schema_entry, 
406                                                               "  DESC '%s'\n", description);
407                         IF_NULL_FAIL_RET(schema_entry);
408                 }
409
410                 if (subClassOf) {
411                         schema_entry = talloc_asprintf_append(schema_entry, 
412                                                               "  SUP %s\n", subClassOf);
413                         IF_NULL_FAIL_RET(schema_entry);
414                 }
415                 
416                 switch (objectClassCategory) {
417                 case 1:
418                         schema_entry = talloc_asprintf_append(schema_entry, 
419                                                               "  STRUCTURAL\n");
420                         IF_NULL_FAIL_RET(schema_entry);
421                         break;
422                 case 2:
423                         schema_entry = talloc_asprintf_append(schema_entry, 
424                                                               "  ABSTRACT\n");
425                         IF_NULL_FAIL_RET(schema_entry);
426                         break;
427                 case 3:
428                         schema_entry = talloc_asprintf_append(schema_entry, 
429                                                               "  AUXILIARY\n");
430                         IF_NULL_FAIL_RET(schema_entry);
431                         break;
432                 }
433
434 #define APPEND_ATTRS(attributes) \
435                 do {                                            \
436                         int k;                                          \
437                         for (k=0; attributes && attributes[k]; k++) { \
438                                 int attr_idx; \
439                                 const char *attr_name = attributes[k];  \
440                                 /* We might have been asked to remap this name, due to a conflict */ \
441                                 for (attr_idx=0; attr_name && attr_map && attr_map[attr_idx].old_attr; attr_idx++) { \
442                                         if (strcasecmp(attr_name, attr_map[attr_idx].old_attr) == 0) { \
443                                                 attr_name =  attr_map[attr_idx].new_attr; \
444                                                 break;                  \
445                                         }                               \
446                                 }                                       \
447                                                                         \
448                                 schema_entry = talloc_asprintf_append(schema_entry, \
449                                                                       " %s", \
450                                                                       attr_name); \
451                                 IF_NULL_FAIL_RET(schema_entry);         \
452                                 if (attributes[k+1]) { \
453                                         schema_entry = talloc_asprintf_append(schema_entry, \
454                                                                               " $"); \
455                                         IF_NULL_FAIL_RET(schema_entry); \
456                                         if (target == TARGET_OPENLDAP && ((k+1)%5 == 0)) { \
457                                                 schema_entry = talloc_asprintf_append(schema_entry, \
458                                                                                       "\n  "); \
459                                                 IF_NULL_FAIL_RET(schema_entry); \
460                                         }                               \
461                                 }                                       \
462                         }                                               \
463                 } while (0)
464
465                 if (must) {
466                         schema_entry = talloc_asprintf_append(schema_entry, 
467                                                               "  MUST (");
468                         IF_NULL_FAIL_RET(schema_entry);
469
470                         APPEND_ATTRS(must);
471
472                         schema_entry = talloc_asprintf_append(schema_entry, 
473                                                               " )\n");
474                         IF_NULL_FAIL_RET(schema_entry);
475                 }
476
477                 if (may) {
478                         schema_entry = talloc_asprintf_append(schema_entry, 
479                                                               "  MAY (");
480                         IF_NULL_FAIL_RET(schema_entry);
481
482                         APPEND_ATTRS(may);
483
484                         schema_entry = talloc_asprintf_append(schema_entry, 
485                                                               " )\n");
486                         IF_NULL_FAIL_RET(schema_entry);
487                 }
488
489                 schema_entry = talloc_asprintf_append(schema_entry, 
490                                                       "  )");
491
492                 switch (target) {
493                 case TARGET_OPENLDAP:
494                         fprintf(out, "%s\n\n", schema_entry);
495                         break;
496                 case TARGET_FEDORA_DS:
497                         fprintf(out, "%s\n", schema_entry);
498                         break;
499                 }
500                 ret.count++;
501         }
502
503         return ret;
504 }
505
506  int main(int argc, const char **argv)
507 {
508         TALLOC_CTX *ctx;
509         struct ldb_cmdline *options;
510         FILE *in = stdin;
511         FILE *out = stdout;
512         struct ldb_context *ldb;
513         struct schema_conv ret;
514         const char *target_str;
515         enum convert_target target;
516
517         ctx = talloc_new(NULL);
518         ldb = ldb_init(ctx, NULL);
519
520         options = ldb_cmdline_process(ldb, argc, argv, usage);
521
522         if (options->input) {
523                 in = fopen(options->input, "r");
524                 if (!in) {
525                         perror(options->input);
526                         exit(1);
527                 }
528         }
529         if (options->output) {
530                 out = fopen(options->output, "w");
531                 if (!out) {
532                         perror(options->output);
533                         exit(1);
534                 }
535         }
536
537         target_str = lp_parm_string(cmdline_lp_ctx, NULL, "convert", "target");
538
539         if (!target_str || strcasecmp(target_str, "openldap") == 0) {
540                 target = TARGET_OPENLDAP;
541         } else if (strcasecmp(target_str, "fedora-ds") == 0) {
542                 target = TARGET_FEDORA_DS;
543         } else {
544                 printf("Unsupported target: %s\n", target_str);
545                 exit(1);
546         }
547
548         ret = process_convert(ldb, target, in, out);
549
550         fclose(in);
551         fclose(out);
552
553         printf("Converted %d records (skipped %d) with %d failures\n", ret.count, ret.skipped, ret.failures);
554
555         return 0;
556 }