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