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