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