Collapse auxillary classes in LDAP schema conversion.
[kai/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 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 "tools/cmdline.h"
38 #include "tools/convert.h"
39 #include "param/param.h"
40 #include "lib/cmdline/popt_common.h"
41
42 struct schema_conv {
43         int count;
44         int skipped;
45         int failures;
46 };
47
48 enum convert_target {
49         TARGET_OPENLDAP,
50         TARGET_FEDORA_DS
51 };
52         
53
54 static void usage(void)
55 {
56         printf("Usage: ad2oLschema <options>\n");
57         printf("\nConvert AD-like LDIF to OpenLDAP schema format\n\n");
58         printf("Options:\n");
59         printf("  -I inputfile     inputfile of mapped OIDs and skipped attributes/ObjectClasses");
60         printf("  -H url           LDB or LDAP server to read schmea from\n");
61         printf("  -O outputfile    outputfile otherwise STDOUT\n");
62         printf("  -o options       pass options like modules to activate\n");
63         printf("              e.g: -o modules:timestamps\n");
64         printf("\n");
65         printf("Converts records from an AD-like LDIF schema into an openLdap formatted schema\n\n");
66         exit(1);
67 }
68
69 static int fetch_attrs_schema(struct ldb_context *ldb, struct ldb_dn *schemadn,
70                               TALLOC_CTX *mem_ctx, 
71                               struct ldb_result **attrs_res)
72 {
73         TALLOC_CTX *local_ctx = talloc_new(mem_ctx);
74         int ret;
75         const char *attrs[] = {
76                 "lDAPDisplayName",
77                 "isSingleValued",
78                 "attributeID",
79                 "attributeSyntax",
80                 "description",          
81                 NULL
82         };
83
84         if (!local_ctx) {
85                 return LDB_ERR_OPERATIONS_ERROR;
86         }
87         
88         /* Downlaod schema */
89         ret = ldb_search(ldb, schemadn, LDB_SCOPE_SUBTREE, 
90                          "objectClass=attributeSchema", 
91                          attrs, attrs_res);
92         if (ret != LDB_SUCCESS) {
93                 printf("Search failed: %s\n", ldb_errstring(ldb));
94                 return LDB_ERR_OPERATIONS_ERROR;
95         }
96         
97         return ret;
98 }
99
100 static const char *oc_attrs[] = {
101         "lDAPDisplayName",
102         "mayContain",
103         "mustContain",
104         "systemMayContain",
105         "systemMustContain",
106         "objectClassCategory",
107         "governsID",
108         "description",
109         "subClassOf",
110         "systemAuxiliaryClass",
111         "auxiliaryClass",
112         NULL
113 };
114
115 static int fetch_oc_recursive(struct ldb_context *ldb, struct ldb_dn *schemadn, 
116                               TALLOC_CTX *mem_ctx, 
117                               struct ldb_result *search_from,
118                               struct ldb_result *res_list)
119 {
120         int i;
121         int ret = 0;
122         for (i=0; i < search_from->count; i++) {
123                 struct ldb_result *res;
124                 const char *name = ldb_msg_find_attr_as_string(search_from->msgs[i], 
125                                                                "lDAPDisplayname", NULL);
126
127                 ret = ldb_search_exp_fmt(ldb, mem_ctx, &res,
128                                         schemadn, LDB_SCOPE_SUBTREE, oc_attrs,
129                                         "(&(&(objectClass=classSchema)(subClassOf=%s))(!(lDAPDisplayName=%s)))",
130                                         name, name);
131                 if (ret != LDB_SUCCESS) {
132                         printf("Search failed: %s\n", ldb_errstring(ldb));
133                         return ret;
134                 }
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         const char *no_attrs[] = { NULL };
202         struct ldb_dn *schemadn;
203         struct ldb_dn *basedn = ldb_dn_new(mem_ctx, ldb, NULL);
204         struct ldb_result *rootdse_res;
205         struct ldb_result *schema_res;
206         int ldb_ret;
207         if (!basedn) {
208                 return NULL;
209         }
210         
211         /* Search for rootdse */
212         ldb_ret = ldb_search(ldb, basedn, LDB_SCOPE_BASE, NULL, rootdse_attrs, &rootdse_res);
213         if (ldb_ret != LDB_SUCCESS) {
214                 ldb_ret = ldb_search(ldb, basedn, LDB_SCOPE_SUBTREE, 
215                                  "(&(objectClass=dMD)(cn=Schema))", 
216                                  no_attrs, &schema_res);
217                 if (ldb_ret) {
218                         printf("cn=Schema Search failed: %s\n", ldb_errstring(ldb));
219                         return NULL;
220                 }
221
222                 talloc_steal(mem_ctx, schema_res);
223
224                 if (schema_res->count != 1) {
225                         printf("Failed to find rootDSE");
226                         return NULL;
227                 }
228                 
229                 schemadn = talloc_steal(mem_ctx, schema_res->msgs[0]->dn);
230                 talloc_free(schema_res);
231                 return schemadn;
232                 
233         }
234         
235         talloc_steal(mem_ctx, rootdse_res);
236
237         if (rootdse_res->count != 1) {
238                 printf("Failed to find rootDSE");
239                 return NULL;
240         }
241         
242         /* Locate schema */
243         schemadn = ldb_msg_find_attr_as_dn(ldb, mem_ctx, rootdse_res->msgs[0], "schemaNamingContext");
244         if (!schemadn) {
245                 return NULL;
246         }
247
248         talloc_free(rootdse_res);
249         return schemadn;
250 }
251
252 static bool merge_attr_list(TALLOC_CTX *mem_ctx, 
253                             struct ldb_message_element *attrs, struct ldb_message_element *new_attrs) 
254 {
255         struct ldb_val *values;
256         if (!new_attrs) {
257                 return true;
258         }
259
260         values = talloc_realloc(mem_ctx, 
261                                 attrs->values, struct ldb_val, attrs->num_values + new_attrs->num_values);
262         
263         attrs->values = values;
264
265         memcpy(&attrs->values[attrs->num_values], new_attrs->values, sizeof(*new_attrs->values) * new_attrs->num_values);
266         attrs->num_values = attrs->num_values + new_attrs->num_values;
267
268         /* Add sort and unique implementation here */
269
270         return true;
271 }
272
273 static bool find_aux_classes(TALLOC_CTX *mem_ctx, struct ldb_context *ldb, struct ldb_dn *schema_dn,
274                              struct ldb_message_element *aux_class, struct ldb_message_element *must, 
275                              struct ldb_message_element *sys_must, struct ldb_message_element *may, 
276                              struct ldb_message_element *sys_may) 
277 {
278         int i, ret;
279         struct ldb_message *msg;
280         struct ldb_result *res;
281
282         for (i=0; aux_class && i < aux_class->num_values; i++) {
283                 ret = ldb_search_exp_fmt(ldb, mem_ctx, &res,
284                                          schema_dn, LDB_SCOPE_SUBTREE, oc_attrs,
285                                          "(&(objectClass=classSchema)(lDAPDisplayName=%s))",
286                                          aux_class->values[i].data);
287                 if (ret != LDB_SUCCESS) {
288                         return false;
289                 }
290
291                 msg = res->msgs[0];
292
293                 if (!merge_attr_list(mem_ctx, must, ldb_msg_find_element(msg, "mustContain"))) {
294                         return false;
295                 }
296                 if (!merge_attr_list(mem_ctx, sys_must,  ldb_msg_find_element(msg, "systemMustContain"))) {
297                         return false;
298                 }
299                 if (!merge_attr_list(mem_ctx, may, ldb_msg_find_element(msg, "mayContain"))) {
300                         return false;
301                 }
302                 if (!merge_attr_list(mem_ctx, sys_may, ldb_msg_find_element(msg, "systemMayContain"))) {
303                         return false;
304                 }
305                 
306                 
307                 if (res->count == 0) {
308                         return false;
309                 }
310
311                 if (!find_aux_classes(mem_ctx, ldb, schema_dn,
312                                       ldb_msg_find_element(msg, "auxiliaryClass"), must, sys_must, may, sys_may)) {
313                         return false;
314                 }
315                 if (!find_aux_classes(mem_ctx, ldb, schema_dn,
316                                       ldb_msg_find_element(msg, "systemAuxiliaryClass"), must, sys_must, may, sys_may)) {
317                         return false;
318                 }
319         }
320         return true;
321 }
322
323
324 #define IF_NULL_FAIL_RET(x) do {     \
325                 if (!x) {               \
326                         ret.failures++; \
327                         return ret;     \
328                 }                       \
329         } while (0) 
330
331
332 static struct schema_conv process_convert(struct ldb_context *ldb, enum convert_target target, FILE *in, FILE *out) 
333 {
334         /* Read list of attributes to skip, OIDs to map */
335         TALLOC_CTX *mem_ctx = talloc_new(ldb);
336         char *line;
337         const char **attrs_skip = NULL;
338         int num_skip = 0;
339         struct oid_map {
340                 char *old_oid;
341                 char *new_oid;
342         } *oid_map = NULL;
343         int num_oid_maps = 0;
344         struct attr_map {
345                 char *old_attr;
346                 char *new_attr;
347         } *attr_map = NULL;
348         int num_attr_maps = 0;  
349         struct ldb_result *attrs_res, *objectclasses_res;
350         struct ldb_dn *schemadn;
351         struct schema_conv ret;
352
353         int ldb_ret, i;
354
355         ret.count = 0;
356         ret.skipped = 0;
357         ret.failures = 0;
358
359         while ((line = afdgets(fileno(in), mem_ctx, 0))) {
360                 /* Blank Line */
361                 if (line[0] == '\0') {
362                         continue;
363                 }
364                 /* Comment */
365                 if (line[0] == '#') {
366                         continue;
367                 }
368                 if (isdigit(line[0])) {
369                         char *p = strchr(line, ':');
370                         IF_NULL_FAIL_RET(p);
371                         p[0] = '\0';
372                         p++;
373                         oid_map = talloc_realloc(mem_ctx, oid_map, struct oid_map, num_oid_maps + 2);
374                         trim_string(line, " ", " ");
375                         oid_map[num_oid_maps].old_oid = talloc_move(oid_map, &line);
376                         trim_string(p, " ", " ");
377                         oid_map[num_oid_maps].new_oid = p;
378                         num_oid_maps++;
379                         oid_map[num_oid_maps].old_oid = NULL;
380                 } else {
381                         char *p = strchr(line, ':');
382                         if (p) {
383                                 /* remap attribute/objectClass */
384                                 p[0] = '\0';
385                                 p++;
386                                 attr_map = talloc_realloc(mem_ctx, attr_map, struct attr_map, num_attr_maps + 2);
387                                 trim_string(line, " ", " ");
388                                 attr_map[num_attr_maps].old_attr = talloc_move(attr_map, &line);
389                                 trim_string(p, " ", " ");
390                                 attr_map[num_attr_maps].new_attr = p;
391                                 num_attr_maps++;
392                                 attr_map[num_attr_maps].old_attr = NULL;
393                         } else {
394                                 /* skip attribute/objectClass */
395                                 attrs_skip = talloc_realloc(mem_ctx, attrs_skip, const char *, num_skip + 2);
396                                 trim_string(line, " ", " ");
397                                 attrs_skip[num_skip] = talloc_move(attrs_skip, &line);
398                                 num_skip++;
399                                 attrs_skip[num_skip] = NULL;
400                         }
401                 }
402         }
403
404         schemadn = find_schema_dn(ldb, mem_ctx);
405         if (!schemadn) {
406                 printf("Failed to find schema DN: %s\n", ldb_errstring(ldb));
407                 ret.failures = 1;
408                 return ret;
409         }
410         
411         ldb_ret = fetch_attrs_schema(ldb, schemadn, mem_ctx, &attrs_res);
412         if (ldb_ret != LDB_SUCCESS) {
413                 printf("Failed to fetch attribute schema: %s\n", ldb_errstring(ldb));
414                 ret.failures = 1;
415                 return ret;
416         }
417         
418         switch (target) {
419         case TARGET_OPENLDAP:
420                 break;
421         case TARGET_FEDORA_DS:
422                 fprintf(out, "dn: cn=schema\n");
423                 break;
424         }
425
426         for (i=0; i < attrs_res->count; i++) {
427                 struct ldb_message *msg = attrs_res->msgs[i];
428
429                 const char *name = ldb_msg_find_attr_as_string(msg, "lDAPDisplayName", NULL);
430                 const char *description = ldb_msg_find_attr_as_string(msg, "description", NULL);
431                 const char *oid = ldb_msg_find_attr_as_string(msg, "attributeID", NULL);
432                 const char *syntax = ldb_msg_find_attr_as_string(msg, "attributeSyntax", NULL);
433                 bool single_value = ldb_msg_find_attr_as_bool(msg, "isSingleValued", false);
434                 const struct syntax_map *map = find_syntax_map_by_ad_oid(syntax);
435                 char *schema_entry = NULL;
436                 int j;
437
438                 if (!name) {
439                         printf("Failed to find lDAPDisplayName for schema DN: %s\n", ldb_dn_get_linearized(msg->dn));
440                         ret.failures++;
441                         continue;
442                 }
443
444                 /* We have been asked to skip some attributes/objectClasses */
445                 if (attrs_skip && str_list_check_ci(attrs_skip, name)) {
446                         ret.skipped++;
447                         continue;
448                 }
449
450                 /* We might have been asked to remap this oid, due to a conflict */
451                 for (j=0; oid && oid_map && oid_map[j].old_oid; j++) {
452                         if (strcasecmp(oid, oid_map[j].old_oid) == 0) {
453                                 oid =  oid_map[j].new_oid;
454                                 break;
455                         }
456                 }
457                 
458                 switch (target) {
459                 case TARGET_OPENLDAP:
460                         schema_entry = talloc_asprintf(mem_ctx, 
461                                                        "attributetype (\n"
462                                                        "  %s\n", oid);
463                         break;
464                 case TARGET_FEDORA_DS:
465                         schema_entry = talloc_asprintf(mem_ctx, 
466                                                        "attributeTypes: (\n"
467                                                        "  %s\n", oid);
468                         break;
469                 }
470                 IF_NULL_FAIL_RET(schema_entry);
471
472                 /* We might have been asked to remap this name, due to a conflict */
473                 for (j=0; name && attr_map && attr_map[j].old_attr; j++) {
474                         if (strcasecmp(name, attr_map[j].old_attr) == 0) {
475                                 name =  attr_map[j].new_attr;
476                                 break;
477                         }
478                 }
479                 
480                 schema_entry = talloc_asprintf_append(schema_entry, 
481                                                       "  NAME '%s'\n", name);
482                 IF_NULL_FAIL_RET(schema_entry);
483
484                 if (description) {
485                         schema_entry = talloc_asprintf_append(schema_entry, 
486                                                               "  DESC %s\n", description);
487                         IF_NULL_FAIL_RET(schema_entry);
488                 }
489
490                 if (map) {
491                         const char *syntax_oid;
492                         if (map->equality) {
493                                 schema_entry = talloc_asprintf_append(schema_entry, 
494                                                                       "  EQUALITY %s\n", map->equality);
495                                 IF_NULL_FAIL_RET(schema_entry);
496                         }
497                         if (map->substring) {
498                                 schema_entry = talloc_asprintf_append(schema_entry, 
499                                                                       "  SUBSTR %s\n", map->substring);
500                                 IF_NULL_FAIL_RET(schema_entry);
501                         }
502                         syntax_oid = map->Standard_OID;
503                         /* We might have been asked to remap this oid,
504                          * due to a conflict, or lack of
505                          * implementation */
506                         for (j=0; syntax_oid && oid_map && oid_map[j].old_oid; j++) {
507                                 if (strcasecmp(syntax_oid, oid_map[j].old_oid) == 0) {
508                                         syntax_oid =  oid_map[j].new_oid;
509                                         break;
510                                 }
511                         }
512                         schema_entry = talloc_asprintf_append(schema_entry, 
513                                                               "  SYNTAX %s\n", syntax_oid);
514                         IF_NULL_FAIL_RET(schema_entry);
515                 }
516
517                 if (single_value) {
518                         schema_entry = talloc_asprintf_append(schema_entry, 
519                                                               "  SINGLE-VALUE\n");
520                         IF_NULL_FAIL_RET(schema_entry);
521                 }
522                 
523                 schema_entry = talloc_asprintf_append(schema_entry, 
524                                                       "  )");
525
526                 switch (target) {
527                 case TARGET_OPENLDAP:
528                         fprintf(out, "%s\n\n", schema_entry);
529                         break;
530                 case TARGET_FEDORA_DS:
531                         fprintf(out, "%s\n", schema_entry);
532                         break;
533                 }
534                 ret.count++;
535         }
536
537         ldb_ret = fetch_objectclass_schema(ldb, schemadn, mem_ctx, &objectclasses_res);
538         if (ldb_ret != LDB_SUCCESS) {
539                 printf("Failed to fetch objectClass schema elements: %s\n", ldb_errstring(ldb));
540                 ret.failures = 1;
541                 return ret;
542         }
543         
544         for (i=0; i < objectclasses_res->count; i++) {
545                 struct ldb_message *msg = objectclasses_res->msgs[i];
546                 const char *name = ldb_msg_find_attr_as_string(msg, "lDAPDisplayName", NULL);
547                 const char *description = ldb_msg_find_attr_as_string(msg, "description", NULL);
548                 const char *oid = ldb_msg_find_attr_as_string(msg, "governsID", NULL);
549                 const char *subClassOf = ldb_msg_find_attr_as_string(msg, "subClassOf", NULL);
550                 int objectClassCategory = ldb_msg_find_attr_as_int(msg, "objectClassCategory", 0);
551                 struct ldb_message_element *must = ldb_msg_find_element(msg, "mustContain");
552                 struct ldb_message_element *sys_must = ldb_msg_find_element(msg, "systemMustContain");
553                 struct ldb_message_element *may = ldb_msg_find_element(msg, "mayContain");
554                 struct ldb_message_element *sys_may = ldb_msg_find_element(msg, "systemMayContain");
555                 struct ldb_message_element *aux_class = ldb_msg_find_element(msg, "auxiliaryClass");
556                 struct ldb_message_element *sys_aux_class = ldb_msg_find_element(msg, "systemAuxiliaryClass");
557                 char *schema_entry = NULL;
558                 int j;
559
560                 if (!name) {
561                         printf("Failed to find lDAPDisplayName for schema DN: %s\n", ldb_dn_get_linearized(msg->dn));
562                         ret.failures++;
563                         continue;
564                 }
565
566                 /* We have been asked to skip some attributes/objectClasses */
567                 if (attrs_skip && str_list_check_ci(attrs_skip, name)) {
568                         ret.skipped++;
569                         continue;
570                 }
571
572                 if (must == NULL) {
573                         must = talloc_zero(mem_ctx, struct ldb_message_element);
574                 }
575
576                 if (may == NULL) {
577                         may = talloc_zero(mem_ctx, struct ldb_message_element);
578                 }
579
580                 if (sys_must == NULL) {
581                         sys_must = talloc_zero(mem_ctx, struct ldb_message_element);
582                 }
583
584                 if (sys_may == NULL) {
585                         sys_may = talloc_zero(mem_ctx, struct ldb_message_element);
586                 }
587
588                 if (!find_aux_classes(mem_ctx, ldb, schemadn, aux_class, must, sys_must, may, sys_may)) {
589                         ret.failures++;
590                         continue;
591                 }
592
593                 if (!find_aux_classes(mem_ctx, ldb, schemadn, sys_aux_class, must, sys_must, may, sys_may)) {
594                         ret.failures++;
595                         continue;
596                 }
597
598                 /* We might have been asked to remap this oid, due to a conflict */
599                 for (j=0; oid_map && oid_map[j].old_oid; j++) {
600                         if (strcasecmp(oid, oid_map[j].old_oid) == 0) {
601                                 oid =  oid_map[j].new_oid;
602                                 break;
603                         }
604                 }
605                 
606                 switch (target) {
607                 case TARGET_OPENLDAP:
608                         schema_entry = talloc_asprintf(mem_ctx, 
609                                                        "objectclass (\n"
610                                                        "  %s\n", oid);
611                         break;
612                 case TARGET_FEDORA_DS:
613                         schema_entry = talloc_asprintf(mem_ctx, 
614                                                        "objectClasses: (\n"
615                                                        "  %s\n", oid);
616                         break;
617                 }
618                 IF_NULL_FAIL_RET(schema_entry);
619                 if (!schema_entry) {
620                         ret.failures++;
621                         break;
622                 }
623
624                 /* We might have been asked to remap this name, due to a conflict */
625                 for (j=0; name && attr_map && attr_map[j].old_attr; j++) {
626                         if (strcasecmp(name, attr_map[j].old_attr) == 0) {
627                                 name =  attr_map[j].new_attr;
628                                 break;
629                         }
630                 }
631                 
632                 schema_entry = talloc_asprintf_append(schema_entry, 
633                                                       "  NAME '%s'\n", name);
634                 IF_NULL_FAIL_RET(schema_entry);
635
636                 if (!schema_entry) return ret;
637
638                 if (description) {
639                         schema_entry = talloc_asprintf_append(schema_entry, 
640                                                               "  DESC %s\n", description);
641                         IF_NULL_FAIL_RET(schema_entry);
642                 }
643
644                 if (subClassOf) {
645                         schema_entry = talloc_asprintf_append(schema_entry, 
646                                                               "  SUP %s\n", subClassOf);
647                         IF_NULL_FAIL_RET(schema_entry);
648                 }
649                 
650                 switch (objectClassCategory) {
651                 case 1:
652                         schema_entry = talloc_asprintf_append(schema_entry, 
653                                                               "  STRUCTURAL\n");
654                         IF_NULL_FAIL_RET(schema_entry);
655                         break;
656                 case 2:
657                         schema_entry = talloc_asprintf_append(schema_entry, 
658                                                               "  ABSTRACT\n");
659                         IF_NULL_FAIL_RET(schema_entry);
660                         break;
661                 case 3:
662                         schema_entry = talloc_asprintf_append(schema_entry, 
663                                                               "  AUXILIARY\n");
664                         IF_NULL_FAIL_RET(schema_entry);
665                         break;
666                 }
667
668 #define APPEND_ATTRS(attributes) \
669                 do {                                            \
670                         int k;                                          \
671                         for (k=0; attributes && k < attributes->num_values; k++) { \
672                                 int attr_idx; \
673                                 const char *attr_name = (const char *)attributes->values[k].data;  \
674                                 /* We might have been asked to remap this name, due to a conflict */ \
675                                 for (attr_idx=0; attr_name && attr_map && attr_map[attr_idx].old_attr; attr_idx++) { \
676                                         if (strcasecmp(attr_name, attr_map[attr_idx].old_attr) == 0) { \
677                                                 attr_name =  attr_map[attr_idx].new_attr; \
678                                                 break;                  \
679                                         }                               \
680                                 }                                       \
681                                                                         \
682                                 schema_entry = talloc_asprintf_append(schema_entry, \
683                                                                       " %s", \
684                                                                       attr_name); \
685                                 IF_NULL_FAIL_RET(schema_entry);         \
686                                 if (k != (attributes->num_values - 1)) { \
687                                         schema_entry = talloc_asprintf_append(schema_entry, \
688                                                                               " $"); \
689                                         IF_NULL_FAIL_RET(schema_entry); \
690                                         if (target == TARGET_OPENLDAP && ((k+1)%5 == 0)) { \
691                                                 schema_entry = talloc_asprintf_append(schema_entry, \
692                                                                                       "\n  "); \
693                                                 IF_NULL_FAIL_RET(schema_entry); \
694                                         }                               \
695                                 }                                       \
696                         }                                               \
697                 } while (0)
698
699                 if ((must && must->values) || (sys_must && sys_must->values)) {
700                         schema_entry = talloc_asprintf_append(schema_entry, 
701                                                               "  MUST (");
702                         IF_NULL_FAIL_RET(schema_entry);
703
704                         APPEND_ATTRS(must);
705                         if (must && must->values && sys_must && sys_must->values) {
706                                 schema_entry = talloc_asprintf_append(schema_entry, \
707                                                                       " $"); \
708                         }
709                         APPEND_ATTRS(sys_must);
710                         
711                         schema_entry = talloc_asprintf_append(schema_entry, 
712                                                               " )\n");
713                         IF_NULL_FAIL_RET(schema_entry);
714                 }
715
716                 if ((may && may->values) || (sys_may && sys_may->values)) {
717                         schema_entry = talloc_asprintf_append(schema_entry, 
718                                                               "  MAY (");
719                         IF_NULL_FAIL_RET(schema_entry);
720
721                         APPEND_ATTRS(may);
722                         if (may && may->values && sys_may && sys_may->values) {
723                                 schema_entry = talloc_asprintf_append(schema_entry, \
724                                                                       " $"); \
725                         }
726                         APPEND_ATTRS(sys_may);
727                         
728                         schema_entry = talloc_asprintf_append(schema_entry, 
729                                                               " )\n");
730                         IF_NULL_FAIL_RET(schema_entry);
731                 }
732
733                 schema_entry = talloc_asprintf_append(schema_entry, 
734                                                       "  )");
735
736                 switch (target) {
737                 case TARGET_OPENLDAP:
738                         fprintf(out, "%s\n\n", schema_entry);
739                         break;
740                 case TARGET_FEDORA_DS:
741                         fprintf(out, "%s\n", schema_entry);
742                         break;
743                 }
744                 ret.count++;
745         }
746
747         return ret;
748 }
749
750  int main(int argc, const char **argv)
751 {
752         TALLOC_CTX *ctx;
753         struct ldb_cmdline *options;
754         FILE *in = stdin;
755         FILE *out = stdout;
756         struct ldb_context *ldb;
757         struct schema_conv ret;
758         const char *target_str;
759         enum convert_target target;
760
761         ctx = talloc_new(NULL);
762         ldb = ldb_init(ctx, NULL);
763
764         options = ldb_cmdline_process(ldb, argc, argv, usage);
765
766         if (options->input) {
767                 in = fopen(options->input, "r");
768                 if (!in) {
769                         perror(options->input);
770                         exit(1);
771                 }
772         }
773         if (options->output) {
774                 out = fopen(options->output, "w");
775                 if (!out) {
776                         perror(options->output);
777                         exit(1);
778                 }
779         }
780
781         target_str = lp_parm_string(cmdline_lp_ctx, NULL, "convert", "target");
782
783         if (!target_str || strcasecmp(target_str, "openldap") == 0) {
784                 target = TARGET_OPENLDAP;
785         } else if (strcasecmp(target_str, "fedora-ds") == 0) {
786                 target = TARGET_FEDORA_DS;
787         } else {
788                 printf("Unsupported target: %s\n", target_str);
789                 exit(1);
790         }
791
792         ret = process_convert(ldb, target, in, out);
793
794         fclose(in);
795         fclose(out);
796
797         printf("Converted %d records (skipped %d) with %d failures\n", ret.count, ret.skipped, ret.failures);
798
799         return 0;
800 }