e926dd1ccc50e476b88452fcc6f1b30db9b6f92a
[kai/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 "param/param.h"
39 #include "lib/cmdline/popt_common.h"
40 #include "dsdb/samdb/samdb.h"
41
42 struct schema_conv {
43         int count;
44         int skipped;
45         int failures;
46 };
47         
48
49 static void usage(void)
50 {
51         printf("Usage: ad2oLschema <options>\n");
52         printf("\nConvert AD-like LDIF to OpenLDAP schema format\n\n");
53         printf("Options:\n");
54         printf("  -I inputfile     inputfile of mapped OIDs and skipped attributes/ObjectClasses");
55         printf("  -H url           LDB or LDAP server to read schmea from\n");
56         printf("  -O outputfile    outputfile otherwise STDOUT\n");
57         printf("  -o options       pass options like modules to activate\n");
58         printf("              e.g: -o modules:timestamps\n");
59         printf("\n");
60         printf("Converts records from an AD-like LDIF schema into an openLdap formatted schema\n\n");
61         exit(1);
62 }
63
64 static struct ldb_dn *find_schema_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx) 
65 {
66         const char *rootdse_attrs[] = {"schemaNamingContext", NULL};
67         struct ldb_dn *schemadn;
68         struct ldb_dn *basedn = ldb_dn_new(mem_ctx, ldb, NULL);
69         struct ldb_result *rootdse_res;
70         struct ldb_result *schema_res;
71         int ldb_ret;
72         
73         if (!basedn) {
74                 return NULL;
75         }
76         
77         /* Search for rootdse */
78         ldb_ret = ldb_search(ldb, mem_ctx, &rootdse_res,
79                              basedn, LDB_SCOPE_BASE, rootdse_attrs, NULL);
80         if (ldb_ret != LDB_SUCCESS) {
81                 ldb_ret = ldb_search(ldb, mem_ctx, &schema_res, basedn, LDB_SCOPE_SUBTREE,
82                                      NULL, "(&(objectClass=dMD)(cn=Schema))");
83                 if (ldb_ret) {
84                         printf("cn=Schema Search failed: %s\n", ldb_errstring(ldb));
85                         return NULL;
86                 }
87
88                 if (schema_res->count != 1) {
89                         talloc_free(schema_res);
90                         printf("Failed to find rootDSE");
91                         return NULL;
92                 }
93                 
94                 schemadn = talloc_steal(mem_ctx, schema_res->msgs[0]->dn);
95                 talloc_free(schema_res);
96                 return schemadn;
97         }
98         
99         if (rootdse_res->count != 1) {
100                 printf("Failed to find rootDSE");
101                 talloc_free(rootdse_res);
102                 return NULL;
103         }
104         
105         /* Locate schema */
106         schemadn = ldb_msg_find_attr_as_dn(ldb, mem_ctx, rootdse_res->msgs[0], "schemaNamingContext");
107         talloc_free(rootdse_res);
108
109         if (!schemadn) {
110                 return NULL;
111         }
112
113         return schemadn;
114 }
115
116
117 static struct schema_conv process_convert(struct ldb_context *ldb, enum dsdb_schema_convert_target target, FILE *in, FILE *out) 
118 {
119         /* Read list of attributes to skip, OIDs to map */
120         TALLOC_CTX *mem_ctx = talloc_new(ldb);
121         char *line;
122         const char **attrs_skip = NULL;
123         int num_skip = 0;
124         struct oid_map {
125                 char *old_oid;
126                 char *new_oid;
127         } *oid_map = NULL;
128         int num_oid_maps = 0;
129         struct attr_map {
130                 char *old_attr;
131                 char *new_attr;
132         } *attr_map = NULL;
133         int num_attr_maps = 0;  
134         struct dsdb_class *objectclass;
135         struct dsdb_attribute *attribute;
136         struct ldb_dn *schemadn;
137         struct schema_conv ret;
138         struct dsdb_schema *schema;
139         const char *seperator;
140         char *error_string;
141
142         int ldb_ret;
143
144         ret.count = 0;
145         ret.skipped = 0;
146         ret.failures = 0;
147
148         while ((line = afdgets(fileno(in), mem_ctx, 0))) {
149                 /* Blank Line */
150                 if (line[0] == '\0') {
151                         continue;
152                 }
153                 /* Comment */
154                 if (line[0] == '#') {
155                         continue;
156                 }
157                 if (isdigit(line[0])) {
158                         char *p = strchr(line, ':');
159                         if (!p) {
160                                 ret.failures++;
161                                 return ret;
162                         }
163                         p[0] = '\0';
164                         p++;
165                         oid_map = talloc_realloc(mem_ctx, oid_map, struct oid_map, num_oid_maps + 2);
166                         trim_string(line, " ", " ");
167                         oid_map[num_oid_maps].old_oid = talloc_move(oid_map, &line);
168                         trim_string(p, " ", " ");
169                         oid_map[num_oid_maps].new_oid = p;
170                         num_oid_maps++;
171                         oid_map[num_oid_maps].old_oid = NULL;
172                 } else {
173                         char *p = strchr(line, ':');
174                         if (p) {
175                                 /* remap attribute/objectClass */
176                                 p[0] = '\0';
177                                 p++;
178                                 attr_map = talloc_realloc(mem_ctx, attr_map, struct attr_map, num_attr_maps + 2);
179                                 trim_string(line, " ", " ");
180                                 attr_map[num_attr_maps].old_attr = talloc_move(attr_map, &line);
181                                 trim_string(p, " ", " ");
182                                 attr_map[num_attr_maps].new_attr = p;
183                                 num_attr_maps++;
184                                 attr_map[num_attr_maps].old_attr = NULL;
185                         } else {
186                                 /* skip attribute/objectClass */
187                                 attrs_skip = talloc_realloc(mem_ctx, attrs_skip, const char *, num_skip + 2);
188                                 trim_string(line, " ", " ");
189                                 attrs_skip[num_skip] = talloc_move(attrs_skip, &line);
190                                 num_skip++;
191                                 attrs_skip[num_skip] = NULL;
192                         }
193                 }
194         }
195
196         schemadn = find_schema_dn(ldb, mem_ctx);
197         if (!schemadn) {
198                 printf("Failed to find schema DN: %s\n", ldb_errstring(ldb));
199                 ret.failures = 1;
200                 return ret;
201         }
202         
203         ldb_ret = dsdb_schema_from_schema_dn(mem_ctx, ldb,
204                                              lp_iconv_convenience(cmdline_lp_ctx),
205                                              schemadn, &schema, &error_string);
206         if (ldb_ret != LDB_SUCCESS) {
207                 printf("Failed to load schema: %s\n", error_string);
208                 ret.failures = 1;
209                 return ret;
210         }
211
212         switch (target) {
213         case TARGET_OPENLDAP:
214                 seperator = "\n  ";
215                 break;
216         case TARGET_FEDORA_DS:
217                 seperator = "\n  ";
218                 fprintf(out, "dn: cn=schema\n");
219                 break;
220         }
221
222         for (attribute=schema->attributes; attribute; attribute = attribute->next) {
223                 const char *name = attribute->lDAPDisplayName;
224                 const char *oid = attribute->attributeID_oid;
225                 const char *syntax = attribute->attributeSyntax_oid;
226                 const char *equality = NULL, *substring = NULL;
227                 bool single_value = attribute->isSingleValued;
228
229                 const struct dsdb_syntax *map = find_syntax_map_by_ad_syntax(attribute->oMSyntax);
230                 char *schema_entry = NULL;
231                 int j;
232
233                 /* We have been asked to skip some attributes/objectClasses */
234                 if (attrs_skip && str_list_check_ci(attrs_skip, name)) {
235                         ret.skipped++;
236                         continue;
237                 }
238
239                 /* We might have been asked to remap this oid, due to a conflict */
240                 for (j=0; oid && oid_map && oid_map[j].old_oid; j++) {
241                         if (strcasecmp(oid, oid_map[j].old_oid) == 0) {
242                                 oid =  oid_map[j].new_oid;
243                                 break;
244                         }
245                 }
246                 
247                 if (map) {
248                         /* We might have been asked to remap this oid,
249                          * due to a conflict, or lack of
250                          * implementation */
251                         syntax = map->ldap_oid;
252                         /* We might have been asked to remap this oid, due to a conflict */
253                         for (j=0; syntax && oid_map && oid_map[j].old_oid; j++) {
254                                 if (strcasecmp(syntax, oid_map[j].old_oid) == 0) {
255                                         syntax =  oid_map[j].new_oid;
256                                         break;
257                                 }
258                         }
259                         
260                         equality = map->equality;
261                         substring = map->substring;
262                 }
263
264                 /* We might have been asked to remap this name, due to a conflict */
265                 for (j=0; name && attr_map && attr_map[j].old_attr; j++) {
266                         if (strcasecmp(name, attr_map[j].old_attr) == 0) {
267                                 name =  attr_map[j].new_attr;
268                                 break;
269                         }
270                 }
271                 
272                 schema_entry = schema_attribute_description(mem_ctx, 
273                                                             target, 
274                                                             seperator, 
275                                                             oid, 
276                                                             name, 
277                                                             equality, 
278                                                             substring, 
279                                                             syntax, 
280                                                             single_value, 
281                                                             false,
282                                                             NULL, NULL,
283                                                             NULL, NULL,
284                                                             false, false);
285
286                 if (schema_entry == NULL) {
287                         ret.failures++;
288                         return ret;
289                 }
290
291                 switch (target) {
292                 case TARGET_OPENLDAP:
293                         fprintf(out, "attributetype %s\n\n", schema_entry);
294                         break;
295                 case TARGET_FEDORA_DS:
296                         fprintf(out, "attributeTypes: %s\n", schema_entry);
297                         break;
298                 }
299                 ret.count++;
300         }
301
302         /* This is already sorted to have 'top' and similar classes first */
303         for (objectclass=schema->classes; objectclass; objectclass = objectclass->next) {
304                 const char *name = objectclass->lDAPDisplayName;
305                 const char *oid = objectclass->governsID_oid;
306                 const char *subClassOf = objectclass->subClassOf;
307                 int objectClassCategory = objectclass->objectClassCategory;
308                 char **must;
309                 char **may;
310                 char *schema_entry = NULL;
311                 const char *objectclass_name_as_list[] = {
312                         objectclass->lDAPDisplayName,
313                         NULL
314                 };
315                 int j;
316                 int attr_idx;
317                 
318                 /* We have been asked to skip some attributes/objectClasses */
319                 if (attrs_skip && str_list_check_ci(attrs_skip, name)) {
320                         ret.skipped++;
321                         continue;
322                 }
323
324                 /* We might have been asked to remap this oid, due to a conflict */
325                 for (j=0; oid_map && oid_map[j].old_oid; j++) {
326                         if (strcasecmp(oid, oid_map[j].old_oid) == 0) {
327                                 oid =  oid_map[j].new_oid;
328                                 break;
329                         }
330                 }
331                 
332                 /* We might have been asked to remap this name, due to a conflict */
333                 for (j=0; name && attr_map && attr_map[j].old_attr; j++) {
334                         if (strcasecmp(name, attr_map[j].old_attr) == 0) {
335                                 name =  attr_map[j].new_attr;
336                                 break;
337                         }
338                 }
339                 
340                 may = dsdb_full_attribute_list(mem_ctx, schema, objectclass_name_as_list, DSDB_SCHEMA_ALL_MAY);
341
342                 for (j=0; may && may[j]; j++) {
343                         /* We might have been asked to remap this name, due to a conflict */ 
344                         for (attr_idx=0; attr_map && attr_map[attr_idx].old_attr; attr_idx++) { 
345                                 if (strcasecmp(may[j], attr_map[attr_idx].old_attr) == 0) { 
346                                         may[j] =  attr_map[attr_idx].new_attr; 
347                                         break;                          
348                                 }                                       
349                         }                                               
350                 }
351
352                 must = dsdb_full_attribute_list(mem_ctx, schema, objectclass_name_as_list, DSDB_SCHEMA_ALL_MUST);
353
354                 for (j=0; must && must[j]; j++) {
355                         /* We might have been asked to remap this name, due to a conflict */ 
356                         for (attr_idx=0; attr_map && attr_map[attr_idx].old_attr; attr_idx++) { 
357                                 if (strcasecmp(must[j], attr_map[attr_idx].old_attr) == 0) { 
358                                         must[j] =  attr_map[attr_idx].new_attr; 
359                                         break;                          
360                                 }                                       
361                         }                                               
362                 }
363
364                 schema_entry = schema_class_description(mem_ctx, target, 
365                                                         seperator,
366                                                         oid, 
367                                                         name,
368                                                         NULL, 
369                                                         subClassOf,
370                                                         objectClassCategory,
371                                                         must,
372                                                         may,
373                                                         NULL);
374                 if (schema_entry == NULL) {
375                         ret.failures++;
376                         return ret;
377                 }
378
379                 switch (target) {
380                 case TARGET_OPENLDAP:
381                         fprintf(out, "objectclass %s\n\n", schema_entry);
382                         break;
383                 case TARGET_FEDORA_DS:
384                         fprintf(out, "objectClasses: %s\n", schema_entry);
385                         break;
386                 }
387                 ret.count++;
388         }
389
390         return ret;
391 }
392
393  int main(int argc, const char **argv)
394 {
395         TALLOC_CTX *ctx;
396         struct ldb_cmdline *options;
397         FILE *in = stdin;
398         FILE *out = stdout;
399         struct ldb_context *ldb;
400         struct schema_conv ret;
401         const char *target_str;
402         enum dsdb_schema_convert_target target;
403
404         ctx = talloc_new(NULL);
405         ldb = ldb_init(ctx, NULL);
406
407         options = ldb_cmdline_process(ldb, argc, argv, usage);
408
409         if (options->input) {
410                 in = fopen(options->input, "r");
411                 if (!in) {
412                         perror(options->input);
413                         exit(1);
414                 }
415         }
416         if (options->output) {
417                 out = fopen(options->output, "w");
418                 if (!out) {
419                         perror(options->output);
420                         exit(1);
421                 }
422         }
423
424         target_str = lp_parm_string(cmdline_lp_ctx, NULL, "convert", "target");
425
426         if (!target_str || strcasecmp(target_str, "openldap") == 0) {
427                 target = TARGET_OPENLDAP;
428         } else if (strcasecmp(target_str, "fedora-ds") == 0) {
429                 target = TARGET_FEDORA_DS;
430         } else {
431                 printf("Unsupported target: %s\n", target_str);
432                 exit(1);
433         }
434
435         ret = process_convert(ldb, target, in, out);
436
437         fclose(in);
438         fclose(out);
439
440         printf("Converted %d records (skipped %d) with %d failures\n", ret.count, ret.skipped, ret.failures);
441
442         return 0;
443 }