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