Paramaterise the seperator in ad2OLschema
[tprouty/samba.git] / source4 / utils / 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 "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                         ret.failures++; \
128                         return ret;     \
129                 }                       \
130         } while (0) 
131
132
133 static struct schema_conv process_convert(struct ldb_context *ldb, enum convert_target target, FILE *in, FILE *out) 
134 {
135         /* Read list of attributes to skip, OIDs to map */
136         TALLOC_CTX *mem_ctx = talloc_new(ldb);
137         char *line;
138         const char **attrs_skip = NULL;
139         int num_skip = 0;
140         struct oid_map {
141                 char *old_oid;
142                 char *new_oid;
143         } *oid_map = NULL;
144         int num_oid_maps = 0;
145         struct attr_map {
146                 char *old_attr;
147                 char *new_attr;
148         } *attr_map = NULL;
149         int num_attr_maps = 0;  
150         struct dsdb_class *objectclass;
151         struct dsdb_attribute *attribute;
152         struct ldb_dn *schemadn;
153         struct schema_conv ret;
154         struct dsdb_schema *schema;
155         const char *seperator;
156         char *error_string;
157
158         int ldb_ret;
159
160         ret.count = 0;
161         ret.skipped = 0;
162         ret.failures = 0;
163
164         while ((line = afdgets(fileno(in), mem_ctx, 0))) {
165                 /* Blank Line */
166                 if (line[0] == '\0') {
167                         continue;
168                 }
169                 /* Comment */
170                 if (line[0] == '#') {
171                         continue;
172                 }
173                 if (isdigit(line[0])) {
174                         char *p = strchr(line, ':');
175                         IF_NULL_FAIL_RET(p);
176                         p[0] = '\0';
177                         p++;
178                         oid_map = talloc_realloc(mem_ctx, oid_map, struct oid_map, num_oid_maps + 2);
179                         trim_string(line, " ", " ");
180                         oid_map[num_oid_maps].old_oid = talloc_move(oid_map, &line);
181                         trim_string(p, " ", " ");
182                         oid_map[num_oid_maps].new_oid = p;
183                         num_oid_maps++;
184                         oid_map[num_oid_maps].old_oid = NULL;
185                 } else {
186                         char *p = strchr(line, ':');
187                         if (p) {
188                                 /* remap attribute/objectClass */
189                                 p[0] = '\0';
190                                 p++;
191                                 attr_map = talloc_realloc(mem_ctx, attr_map, struct attr_map, num_attr_maps + 2);
192                                 trim_string(line, " ", " ");
193                                 attr_map[num_attr_maps].old_attr = talloc_move(attr_map, &line);
194                                 trim_string(p, " ", " ");
195                                 attr_map[num_attr_maps].new_attr = p;
196                                 num_attr_maps++;
197                                 attr_map[num_attr_maps].old_attr = NULL;
198                         } else {
199                                 /* skip attribute/objectClass */
200                                 attrs_skip = talloc_realloc(mem_ctx, attrs_skip, const char *, num_skip + 2);
201                                 trim_string(line, " ", " ");
202                                 attrs_skip[num_skip] = talloc_move(attrs_skip, &line);
203                                 num_skip++;
204                                 attrs_skip[num_skip] = NULL;
205                         }
206                 }
207         }
208
209         schemadn = find_schema_dn(ldb, mem_ctx);
210         if (!schemadn) {
211                 printf("Failed to find schema DN: %s\n", ldb_errstring(ldb));
212                 ret.failures = 1;
213                 return ret;
214         }
215         
216         ldb_ret = dsdb_schema_from_schema_dn(mem_ctx, ldb,
217                                              lp_iconv_convenience(cmdline_lp_ctx),
218                                              schemadn, &schema, &error_string);
219         if (ldb_ret != LDB_SUCCESS) {
220                 printf("Failed to load schema: %s\n", error_string);
221                 ret.failures = 1;
222                 return ret;
223         }
224
225         switch (target) {
226         case TARGET_OPENLDAP:
227                 seperator = "\n  ";
228                 break;
229         case TARGET_FEDORA_DS:
230                 seperator = "\n  ";
231                 fprintf(out, "dn: cn=schema\n");
232                 break;
233         }
234
235         for (attribute=schema->attributes; attribute; attribute = attribute->next) {
236                 const char *name = attribute->lDAPDisplayName;
237                 const char *description = attribute->adminDescription;
238                 const char *oid = attribute->attributeID_oid;
239                 const char *syntax = attribute->attributeSyntax_oid;
240                 bool single_value = attribute->isSingleValued;
241
242                 const struct syntax_map *const_map = find_syntax_map_by_ad_oid(syntax);
243                 struct syntax_map map, *map_p = NULL;
244                 char *schema_entry = NULL;
245                 int j;
246
247                 /* We have been asked to skip some attributes/objectClasses */
248                 if (attrs_skip && str_list_check_ci(attrs_skip, name)) {
249                         ret.skipped++;
250                         continue;
251                 }
252
253                 /* We might have been asked to remap this oid, due to a conflict */
254                 for (j=0; oid && oid_map && oid_map[j].old_oid; j++) {
255                         if (strcasecmp(oid, oid_map[j].old_oid) == 0) {
256                                 oid =  oid_map[j].new_oid;
257                                 break;
258                         }
259                 }
260                 
261                 if (const_map) {
262                         map = *const_map;
263                         
264                         /* We might have been asked to remap this oid,
265                          * due to a conflict, or lack of
266                          * implementation */
267                         for (j=0; map.Standard_OID && oid_map && oid_map[j].old_oid; j++) {
268                                 if (strcasecmp(map.Standard_OID, oid_map[j].old_oid) == 0) {
269                                         map.Standard_OID =  oid_map[j].new_oid;
270                                         break;
271                                 }
272                         }
273
274                         map_p = &map;
275                 }
276
277                 /* We might have been asked to remap this name, due to a conflict */
278                 for (j=0; name && attr_map && attr_map[j].old_attr; j++) {
279                         if (strcasecmp(name, attr_map[j].old_attr) == 0) {
280                                 name =  attr_map[j].new_attr;
281                                 break;
282                         }
283                 }
284                 
285                 switch (target) {
286                 case TARGET_OPENLDAP:
287                         schema_entry = talloc_asprintf(mem_ctx, 
288                                                        "attributetype (");
289                         break;
290                 case TARGET_FEDORA_DS:
291                         schema_entry = talloc_asprintf(mem_ctx, 
292                                                        "attributeTypes: (");
293                         break;
294                 }
295                 IF_NULL_FAIL_RET(schema_entry);
296
297                 schema_entry = talloc_asprintf_append(schema_entry, 
298                                                       "%s%s%s", seperator, oid, seperator);
299
300                 schema_entry = talloc_asprintf_append(schema_entry, 
301                                                       "NAME '%s'%s", name, seperator);
302                 IF_NULL_FAIL_RET(schema_entry);
303
304                 if (description) {
305 #if 0 /* If you want to re-enable this, you must first figure out a sane escaping of ' in the description */
306                         schema_entry = talloc_asprintf_append(schema_entry, 
307                                                               "DESC '%s' ", description);
308                         IF_NULL_FAIL_RET(schema_entry);
309 #endif
310                 }
311
312                 if (map_p) {
313                         if (map_p->equality) {
314                                 schema_entry = talloc_asprintf_append(schema_entry, 
315                                                                       "EQUALITY %s%s", map_p->equality, seperator);
316                                 IF_NULL_FAIL_RET(schema_entry);
317                         }
318                         if (map_p->substring) {
319                                 schema_entry = talloc_asprintf_append(schema_entry, 
320                                                                       "SUBSTR %s%s", map_p->substring, seperator);
321                                 IF_NULL_FAIL_RET(schema_entry);
322                         }
323
324                         syntax = map_p->Standard_OID;
325                 }
326
327                 schema_entry = talloc_asprintf_append(schema_entry, 
328                                                       "SYNTAX %s%s", syntax, seperator);
329                 IF_NULL_FAIL_RET(schema_entry);
330
331                 if (single_value) {
332                         schema_entry = talloc_asprintf_append(schema_entry, 
333                                                               "SINGLE-VALUE%s", seperator);
334                         IF_NULL_FAIL_RET(schema_entry);
335                 }
336                 
337                 schema_entry = talloc_asprintf_append(schema_entry, 
338                                                       ")");
339
340                 switch (target) {
341                 case TARGET_OPENLDAP:
342                         fprintf(out, "%s\n\n", schema_entry);
343                         break;
344                 case TARGET_FEDORA_DS:
345                         fprintf(out, "%s\n", schema_entry);
346                         break;
347                 }
348                 ret.count++;
349         }
350
351         /* This is already sorted to have 'top' and similar classes first */
352         for (objectclass=schema->classes; objectclass; objectclass = objectclass->next) {
353                 const char *name = objectclass->lDAPDisplayName;
354                 const char *description = objectclass->adminDescription;
355                 const char *oid = objectclass->governsID_oid;
356                 const char *subClassOf = objectclass->subClassOf;
357                 int objectClassCategory = objectclass->objectClassCategory;
358                 char **must;
359                 char **may;
360                 char *schema_entry = NULL;
361                 const char *objectclass_name_as_list[] = {
362                         objectclass->lDAPDisplayName,
363                         NULL
364                 };
365                 int j;
366                 
367                 /* We have been asked to skip some attributes/objectClasses */
368                 if (attrs_skip && str_list_check_ci(attrs_skip, name)) {
369                         ret.skipped++;
370                         continue;
371                 }
372
373                 /* We might have been asked to remap this oid, due to a conflict */
374                 for (j=0; oid_map && oid_map[j].old_oid; j++) {
375                         if (strcasecmp(oid, oid_map[j].old_oid) == 0) {
376                                 oid =  oid_map[j].new_oid;
377                                 break;
378                         }
379                 }
380                 
381                 /* We might have been asked to remap this name, due to a conflict */
382                 for (j=0; name && attr_map && attr_map[j].old_attr; j++) {
383                         if (strcasecmp(name, attr_map[j].old_attr) == 0) {
384                                 name =  attr_map[j].new_attr;
385                                 break;
386                         }
387                 }
388                 
389                 may = dsdb_full_attribute_list(mem_ctx, schema, objectclass_name_as_list, DSDB_SCHEMA_ALL_MAY);
390
391                 must = dsdb_full_attribute_list(mem_ctx, schema, objectclass_name_as_list, DSDB_SCHEMA_ALL_MUST);
392
393                 switch (target) {
394                 case TARGET_OPENLDAP:
395                         schema_entry = talloc_asprintf(mem_ctx, 
396                                                        "objectclass (");
397                         break;
398                 case TARGET_FEDORA_DS:
399                         schema_entry = talloc_asprintf(mem_ctx, 
400                                                        "objectClasses: (");
401                         break;
402                 }
403                 schema_entry = talloc_asprintf_append(schema_entry, 
404                                                       "%s%s%s", seperator, oid, seperator);
405                                                       
406                 IF_NULL_FAIL_RET(schema_entry);
407                 if (!schema_entry) {
408                         ret.failures++;
409                         break;
410                 }
411
412                 schema_entry = talloc_asprintf_append(schema_entry, 
413                                                       "NAME '%s'%s", name, seperator);
414                 IF_NULL_FAIL_RET(schema_entry);
415
416                 if (!schema_entry) return ret;
417
418                 if (description) {
419                         schema_entry = talloc_asprintf_append(schema_entry, 
420                                                               "DESC '%s'%s", description, seperator);
421                         IF_NULL_FAIL_RET(schema_entry);
422                 }
423
424                 if (subClassOf) {
425                         schema_entry = talloc_asprintf_append(schema_entry, 
426                                                               "SUP %s%s", subClassOf, seperator);
427                         IF_NULL_FAIL_RET(schema_entry);
428                 }
429                 
430                 switch (objectClassCategory) {
431                 case 1:
432                         schema_entry = talloc_asprintf_append(schema_entry, 
433                                                               "STRUCTURAL%s", seperator);
434                         IF_NULL_FAIL_RET(schema_entry);
435                         break;
436                 case 2:
437                         schema_entry = talloc_asprintf_append(schema_entry, 
438                                                               "ABSTRACT%s", seperator);
439                         IF_NULL_FAIL_RET(schema_entry);
440                         break;
441                 case 3:
442                         schema_entry = talloc_asprintf_append(schema_entry, 
443                                                               "AUXILIARY%s", seperator);
444                         IF_NULL_FAIL_RET(schema_entry);
445                         break;
446                 }
447
448 #define APPEND_ATTRS(attributes) \
449                 do {                                            \
450                         int k;                                          \
451                         for (k=0; attributes && attributes[k]; k++) { \
452                                 int attr_idx; \
453                                 const char *attr_name = attributes[k];  \
454                                 /* We might have been asked to remap this name, due to a conflict */ \
455                                 for (attr_idx=0; attr_name && attr_map && attr_map[attr_idx].old_attr; attr_idx++) { \
456                                         if (strcasecmp(attr_name, attr_map[attr_idx].old_attr) == 0) { \
457                                                 attr_name =  attr_map[attr_idx].new_attr; \
458                                                 break;                  \
459                                         }                               \
460                                 }                                       \
461                                                                         \
462                                 schema_entry = talloc_asprintf_append(schema_entry, \
463                                                                       "%s ", \
464                                                                       attr_name); \
465                                 IF_NULL_FAIL_RET(schema_entry);         \
466                                 if (attributes[k+1]) { \
467                                         IF_NULL_FAIL_RET(schema_entry); \
468                                         if (target == TARGET_OPENLDAP && ((k+1)%5 == 0)) { \
469                                                 schema_entry = talloc_asprintf_append(schema_entry, \
470                                                                                       "$%s ", seperator); \
471                                                 IF_NULL_FAIL_RET(schema_entry); \
472                                         } else {                        \
473                                                 schema_entry = talloc_asprintf_append(schema_entry, \
474                                                                                       "$ "); \
475                                         }                               \
476                                 }                                       \
477                         }                                               \
478                 } while (0)
479
480                 if (must) {
481                         schema_entry = talloc_asprintf_append(schema_entry, 
482                                                               "MUST ( ");
483                         IF_NULL_FAIL_RET(schema_entry);
484
485                         APPEND_ATTRS(must);
486
487                         schema_entry = talloc_asprintf_append(schema_entry, 
488                                                               ")%s", seperator);
489                         IF_NULL_FAIL_RET(schema_entry);
490                 }
491
492                 if (may) {
493                         schema_entry = talloc_asprintf_append(schema_entry, 
494                                                               "MAY ( ");
495                         IF_NULL_FAIL_RET(schema_entry);
496
497                         APPEND_ATTRS(may);
498
499                         schema_entry = talloc_asprintf_append(schema_entry, 
500                                                               ")%s", seperator);
501                         IF_NULL_FAIL_RET(schema_entry);
502                 }
503
504                 schema_entry = talloc_asprintf_append(schema_entry, 
505                                                       ")");
506
507                 switch (target) {
508                 case TARGET_OPENLDAP:
509                         fprintf(out, "%s\n\n", schema_entry);
510                         break;
511                 case TARGET_FEDORA_DS:
512                         fprintf(out, "%s\n", schema_entry);
513                         break;
514                 }
515                 ret.count++;
516         }
517
518         return ret;
519 }
520
521  int main(int argc, const char **argv)
522 {
523         TALLOC_CTX *ctx;
524         struct ldb_cmdline *options;
525         FILE *in = stdin;
526         FILE *out = stdout;
527         struct ldb_context *ldb;
528         struct schema_conv ret;
529         const char *target_str;
530         enum convert_target target;
531
532         ctx = talloc_new(NULL);
533         ldb = ldb_init(ctx, NULL);
534
535         options = ldb_cmdline_process(ldb, argc, argv, usage);
536
537         if (options->input) {
538                 in = fopen(options->input, "r");
539                 if (!in) {
540                         perror(options->input);
541                         exit(1);
542                 }
543         }
544         if (options->output) {
545                 out = fopen(options->output, "w");
546                 if (!out) {
547                         perror(options->output);
548                         exit(1);
549                 }
550         }
551
552         target_str = lp_parm_string(cmdline_lp_ctx, NULL, "convert", "target");
553
554         if (!target_str || strcasecmp(target_str, "openldap") == 0) {
555                 target = TARGET_OPENLDAP;
556         } else if (strcasecmp(target_str, "fedora-ds") == 0) {
557                 target = TARGET_FEDORA_DS;
558         } else {
559                 printf("Unsupported target: %s\n", target_str);
560                 exit(1);
561         }
562
563         ret = process_convert(ldb, target, in, out);
564
565         fclose(in);
566         fclose(out);
567
568         printf("Converted %d records (skipped %d) with %d failures\n", ret.count, ret.skipped, ret.failures);
569
570         return 0;
571 }