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