dd7e04270afe2d4d5f80cd846a5cbcb6ba5d1e38
[ira/wip.git] / testprogs / ejs / minschema.js
1 #!/bin/sh
2 exec smbscript "$0" ${1+"$@"}
3 /*
4   work out the minimal schema for a set of objectclasses 
5 */
6
7 libinclude("base.js");
8
9 var ldb = ldb_init();
10
11 var options = GetOptions(ARGV, 
12                          "POPT_AUTOHELP",
13                          "POPT_COMMON_SAMBA",
14                          "POPT_COMMON_CREDENTIALS",
15                          "verbose");
16 if (options == undefined) {
17    println("Failed to parse options");
18    return -1;
19 }
20 verbose = options["verbose"];
21
22 if (options.ARGV.length != 2) {
23    println("Usage: minschema.js <URL> <classfile>");
24    return -1;
25 }
26
27 var url = options.ARGV[0];
28 var classfile = options.ARGV[1];
29
30
31 var ok = ldb.connect(url);
32 assert(ok);
33
34 objectclasses = new Object();
35 attributes = new Object();
36 rootDse = new Object();
37
38
39 /* the attributes we need for objectclasses */
40 class_attrs = new Array("objectClass", 
41                         "auxiliaryClass", "systemAuxiliaryClass",
42                         "possSuperiors", "systemPossSuperiors",
43                         "lDAPDisplayName", "governsID",
44                         "rDNAttID", "mustContain", "systemMustContain",
45                         "mayContain", "systemMayContain",
46                         "objectClassCategory", "subClassOf", 
47                         "defaultObjectCategory", "defaultHidingValue", 
48                         "systemFlags", "systemOnly", "defaultSecurityDescriptor",
49                         "objectCategory", "possibleInferiors", "displaySpecification",
50                         "schemaIDGUID");
51
52 attrib_attrs = new Array("objectClass", "lDAPDisplayName", 
53                          "isSingleValued", "linkID", "systemFlags", "systemOnly",
54                          "schemaIDGUID", "adminDisplayName", "attributeID",
55                          "attributeSyntax");
56
57 /*
58   notes:
59
60   objectClassCategory 
61       1: structural
62       2: abstract
63       3: auxiliary
64 */
65
66
67 /*
68   print only if verbose is set
69 */
70 function dprintf() {
71         if (verbose != undefined) {
72                 print(vsprintf(arguments));
73         }
74 }
75
76 /*
77   create an objectclass object
78 */
79 function obj_objectClass(name) {
80         var o = new Object();
81         o.name = name;
82         return o;
83 }
84
85 /*
86   create an attribute object
87 */
88 function obj_attribute(name) {
89         var o = new Object();
90         o.name = name;
91         return o;
92 }
93
94
95 syntaxmap = new Object();
96
97 syntaxmap['2.5.5.1']  = '1.3.6.1.4.1.1466.115.121.1.12';
98 syntaxmap['2.5.5.2']  = '1.3.6.1.4.1.1466.115.121.1.38';
99 syntaxmap['2.5.5.3']  = '1.2.840.113556.1.4.1362';
100 syntaxmap['2.5.5.4']  = '1.2.840.113556.1.4.905';
101 syntaxmap['2.5.5.5']  = '1.3.6.1.4.1.1466.115.121.1.26';
102 syntaxmap['2.5.5.6']  = '1.3.6.1.4.1.1466.115.121.1.36';
103 syntaxmap['2.5.5.7']  = '1.2.840.113556.1.4.903';
104 syntaxmap['2.5.5.8']  = '1.3.6.1.4.1.1466.115.121.1.7';
105 syntaxmap['2.5.5.9']  = '1.3.6.1.4.1.1466.115.121.1.27';
106 syntaxmap['2.5.5.10'] = '1.3.6.1.4.1.1466.115.121.1.40';
107 syntaxmap['2.5.5.11'] = '1.3.6.1.4.1.1466.115.121.1.24';
108 syntaxmap['2.5.5.12'] = '1.3.6.1.4.1.1466.115.121.1.15';
109 syntaxmap['2.5.5.13'] = '1.3.6.1.4.1.1466.115.121.1.43';
110 syntaxmap['2.5.5.14'] = '1.2.840.113556.1.4.904';
111 syntaxmap['2.5.5.15'] = '1.2.840.113556.1.4.907';
112 syntaxmap['2.5.5.16'] = '1.2.840.113556.1.4.906';
113 syntaxmap['2.5.5.17'] = '1.3.6.1.4.1.1466.115.121.1.40';
114
115 /*
116   map some attribute syntaxes from some apparently MS specific
117   syntaxes to the standard syntaxes
118 */
119 function map_attribute_syntax(s) {
120         if (syntaxmap[s] != undefined) {
121                 return syntaxmap[s];
122         }
123         return s;
124 }
125
126
127 /*
128   fix a string DN to use ${BASEDN}
129 */
130 function fix_dn(dn) {
131         var s = strstr(dn, rootDse.defaultNamingContext);
132         if (s == NULL) {
133                 return dn;
134         }
135         return substr(dn, 0, strlen(dn) - strlen(s)) + "${BASEDN}";
136 }
137
138 /*
139   dump an object as ldif
140 */
141 function write_ldif_one(o, attrs) {
142         var i;
143         printf("dn: CN=%s,CN=Schema,CN=Configuration,${BASEDN}\n", o.name);
144         printf("cn: %s\n", o.name);
145         printf("name: %s\n", o.name);
146         for (i=0;i<attrs.length;i++) {
147                 var a = attrs[i];
148                 if (o[a] == undefined) {
149                         continue;
150                 }
151                 var v = o[a];
152                 if (typeof(v) == "string") {
153                         v = new Array(v);
154                 }
155                 var j;
156                 for (j=0;j<v.length;j++) {
157                         printf("%s: %s\n", a, fix_dn(v[j]));
158                 }
159         }
160         printf("\n");
161 }
162
163 /*
164   dump an array of objects as ldif
165 */
166 function write_ldif(o, attrs) {
167         var i;
168         for (i in o) {
169                 write_ldif_one(o[i], attrs);
170         }
171 }
172
173
174 /*
175   create a testDN based an an example DN
176   the idea is to ensure we obey any structural rules
177 */
178 function create_testdn(exampleDN) {
179         var a = split(",", exampleDN);
180         a[0] = "CN=TestDN";
181         return join(",", a);
182 }
183
184 /*
185   find the properties of an objectclass
186  */
187 function find_objectclass_properties(ldb, o) {
188         var res = ldb.search(
189                 sprintf("(ldapDisplayName=%s)", o.name),
190                 rootDse.schemaNamingContext, ldb.SCOPE_SUBTREE, class_attrs);
191         assert(res != undefined);
192         assert(res.length == 1);
193         var msg = res[0];
194         var a;
195         for (a in msg) {
196                 o[a] = msg[a];
197         }
198 }
199
200 /*
201   find the properties of an attribute
202  */
203 function find_attribute_properties(ldb, o) {
204         var res = ldb.search(
205                 sprintf("(ldapDisplayName=%s)", o.name),
206                 rootDse.schemaNamingContext, ldb.SCOPE_SUBTREE, attrib_attrs);
207         assert(res != undefined);
208         assert(res.length == 1);
209         var msg = res[0];
210         var a;
211         for (a in msg) {
212                 o[a] = msg[a];
213         }
214 }
215
216 /*
217   find the auto-created properties of an objectclass. Only works for classes
218   that can be created using just a DN and the objectclass
219  */
220 function find_objectclass_auto(ldb, o) {
221         if (o["exampleDN"] == undefined) {
222                 return;
223         }
224         var testdn = create_testdn(o.exampleDN);
225         var ok;
226
227         dprintf("testdn is '%s'\n", testdn);
228
229         var ldif = "dn: " + testdn;
230         ldif = ldif + "\nobjectClass: " + o.name;
231         ok = ldb.add(ldif);
232         if (!ok) {
233                 dprintf("error adding %s: %s\n", o.name, ldb.errstring());
234                 dprintf("%s\n", ldif);
235                 return;
236         }
237
238         var res = ldb.search("", testdn, ldb.SCOPE_BASE);
239         ok = ldb.del(testdn);
240         assert(ok);
241
242         var a;
243         for (a in res[0]) {
244                 attributes[a].autocreate = true;
245         }
246 }
247
248
249 /*
250   look at auxiliary information from a class to intuit the existance of more
251   classes needed for a minimal schema
252 */
253 function expand_objectclass(ldb, o) {
254         var attrs = new Array("auxiliaryClass", "systemAuxiliaryClass",
255                               "possSuperiors", "systemPossSuperiors",
256                               "subClassOf");
257         var res = ldb.search(
258                 sprintf("(&(objectClass=classSchema)(ldapDisplayName=%s))", o.name),
259                 rootDse.schemaNamingContext, ldb.SCOPE_SUBTREE, attrs);
260         var a;
261         dprintf("Expanding class %s\n", o.name);
262         assert(res != undefined);
263         assert(res.length == 1);
264         var msg = res[0];
265         for (a=0;a<attrs.length;a++) {
266                 var aname = attrs[a];
267                 if (msg[aname] == undefined) {
268                         continue;
269                 }
270                 var list = msg[aname];
271                 if (typeof(list) == "string") {
272                         list = new Array(msg[aname]);
273                 }
274                 var i;
275                 for (i=0;i<list.length;i++) {
276                         var name = list[i];
277                         if (objectclasses[name] == undefined) {
278                                 dprintf("Found new objectclass '%s'\n", name);
279                                 objectclasses[name] = obj_objectClass(name);
280                         }
281                 }
282         }
283 }
284
285
286 /*
287   add the must and may attributes from an objectclass to the full list
288   of attributes
289 */
290 function add_objectclass_attributes(ldb, class) {
291         var attrs = new Array("mustContain", "systemMustContain", 
292                               "mayContain", "systemMayContain");
293         var i;
294         for (i=0;i<attrs.length;i++) {
295                 var aname = attrs[i];
296                 if (class[aname] == undefined) {
297                         continue;
298                 }
299                 var alist = class[aname];
300                 if (typeof(alist) == "string") {
301                         alist = new Array(alist);
302                 }
303                 var j;
304                 var len = alist.length;
305                 for (j=0;j<len;j++) {
306                         var a = alist[j];
307                         if (attributes[a] == undefined) {
308                                 attributes[a] = obj_attribute(a);
309                         }
310                 }
311         }
312 }
313
314
315 /*
316   process an individual record, working out what attributes it has
317 */
318 function walk_dn(ldb, dn) {
319         /* get a list of all possible attributes for this object */
320         var attrs = new Array("allowedAttributes");
321         var res = ldb.search("objectClass=*", dn, ldb.SCOPE_BASE, attrs);
322         if (res == undefined) {
323                 dprintf("Unable to fetch allowedAttributes for '%s' - %s\n", 
324                        dn, ldb.errstring());
325                 return;
326         }
327         var allattrs = res[0].allowedAttributes;
328         res = ldb.search("objectClass=*", dn, ldb.SCOPE_BASE, allattrs);
329         if (res == undefined) {
330                 dprintf("Unable to fetch all attributes for '%s' - %s\n", 
331                        dn, ldb.errstring());
332                 return;
333         }
334         var a;
335         var msg = res[0];
336         for (a in msg) {
337                 if (attributes[a] == undefined) {
338                         attributes[a] = obj_attribute(a);
339                 }
340         }
341 }
342
343 /*
344   walk a naming context, looking for all records
345 */
346 function walk_naming_context(ldb, namingContext) {
347         var attrs = new Array("objectClass");
348         var res = ldb.search("objectClass=*", namingContext, ldb.SCOPE_DEFAULT, attrs);
349         if (res == undefined) {
350                 dprintf("Unable to fetch objectClasses for '%s' - %s\n", 
351                        namingContext, ldb.errstring());
352                 return;
353         }
354         var r;
355         for (r=0;r<res.length;r++) {
356                 var msg = res[r].objectClass;
357                 var c;
358                 for (c=0;c<msg.length;c++) {
359                         var objectClass = msg[c];
360                         if (objectclasses[objectClass] == undefined) {
361                                 objectclasses[objectClass] = obj_objectClass(objectClass);
362                                 objectclasses[objectClass].exampleDN = res[r].dn;
363                         }
364                 }
365                 walk_dn(ldb, res[r].dn);
366         }
367 }
368
369 /*
370   trim the may attributes for an objectClass
371 */
372 function trim_objectclass_attributes(ldb, class) {
373         /* not implemented yet */
374 }
375
376 /*
377   load the basic attributes of an objectClass
378 */
379 function build_objectclass(ldb, name) {
380         var attrs = new Array("name");
381         var res = ldb.search(
382                 sprintf("(&(objectClass=classSchema)(ldapDisplayName=%s))", name),
383                 rootDse.schemaNamingContext, ldb.SCOPE_SUBTREE, attrs);
384         if (res == undefined) {
385                 dprintf("unknown class '%s'\n", name);
386                 return undefined;
387         }
388         if (res.length == 0) {
389                 dprintf("unknown class '%s'\n", name);
390                 return undefined;
391         }
392         return obj_objectClass(name);
393 }
394
395 /*
396   append 2 lists
397 */
398 function list_append(a1, a2) {
399         var i;
400         if (a1 == undefined) {
401                 return a2;
402         }
403         if (a2 == undefined) {
404                 return a1;
405         }
406         for (i=0;i<a2.length;i++) {
407                 a1[a1.length] = a2[i];
408         }
409         return a1;
410 }
411
412 /*
413   form a coalesced attribute list
414 */
415 function attribute_list(class, attr1, attr2) {
416         var a1 = class[attr1];
417         var a2 = class[attr2];
418         var i;
419         if (typeof(a1) == "string") {
420                 a1 = new Array(a1);
421         }
422         if (typeof(a2) == "string") {
423                 a2 = new Array(a2);
424         }
425         return list_append(a1, a2);
426 }
427
428 /*
429   write out a list in aggregate form
430 */
431 function aggregate_list(name, list) {
432         if (list == undefined) {
433                 return;
434         }
435         var i;
436         printf("%s ( ", name);
437         for (i=0;i<list.length;i++) {
438                 printf("%s ", list[i]);
439                 if (i < (list.length - 1)) {
440                         printf("$ ");
441                 }
442         }
443         printf(") ");
444 }
445
446 /*
447   write the aggregate record for an objectclass
448 */
449 function write_aggregate_objectclass(class) {
450         printf("objectClasses: ( %s NAME '%s' ", class.governsID, class.name);
451         if (class['subClassOf'] != undefined) {
452                 printf("SUP %s ", class['subClassOf']);
453         }
454         if (class.objectClassCategory == 1) {
455                 printf("STRUCTURAL ");
456         } else if (class.objectClassCategory == 2) {
457                 printf("ABSTRACT ");
458         } else if (class.objectClassCategory == 3) {
459                 printf("AUXILIARY ");
460         }
461
462         var list;
463
464         list = attribute_list(class, "systemMustContain", "mustContain");
465         aggregate_list("MUST", list);
466
467         list = attribute_list(class, "systemMayContain", "mayContain");
468         aggregate_list("MAY", list);
469
470         printf(")\n");
471 }
472
473
474 /*
475   write the aggregate record for an ditcontentrule
476 */
477 function write_aggregate_ditcontentrule(class) {
478         var list = attribute_list(class, "auxiliaryClass", "systemAuxiliaryClass");
479         var i;
480         if (list == undefined) {
481                 return;
482         }
483
484         printf("dITContentRules: ( %s NAME '%s' ", class.governsID, class.name);
485
486         aggregate_list("AUX", list);
487
488         var may_list = undefined;
489         var must_list = undefined;
490
491         for (i=0;i<list.length;i++) {
492                 var c = list[i];
493                 var list2;
494                 list2 = attribute_list(objectclasses[c], 
495                                        "mayContain", "systemMayContain");
496                 may_list = list_append(may_list, list2);
497                 list2 = attribute_list(objectclasses[c], 
498                                        "mustContain", "systemMustContain");
499                 must_list = list_append(must_list, list2);
500         }
501
502         aggregate_list("MUST", must_list);
503         aggregate_list("MAY", may_list);
504
505         printf(")\n");
506 }
507
508 /*
509   write the aggregate record for an attribute
510 */
511 function write_aggregate_attribute(attrib) {
512         printf("attributeTypes: ( %s NAME '%s' SYNTAX '%s' ", 
513                attrib.attributeID, attrib.name, 
514                map_attribute_syntax(attrib.attributeSyntax));
515         if (attrib['isSingleValued'] == "TRUE") {
516                 printf("SINGLE-VALUE ");
517         }
518         printf(")\n");
519 }
520
521
522 /*
523   write the aggregate record
524 */
525 function write_aggregate() {
526         printf("dn: CN=Aggregate,CN=Schema,CN=Configuration,${BASEDN}\n");
527         print("objectClass: top
528 objectClass: subSchema
529 cn: Aggregate
530 instanceType: 4
531 name: Aggregate
532 objectCategory: CN=SubSchema,CN=Schema,CN=Configuration,${BASEDN}
533 ");
534         for (i in objectclasses) {
535                 write_aggregate_objectclass(objectclasses[i]);
536         }
537         for (i in attributes) {
538                 write_aggregate_attribute(attributes[i]);
539         }
540         for (i in objectclasses) {
541                 write_aggregate_ditcontentrule(objectclasses[i]);
542         }
543 }
544
545 /*
546   load a list from a file
547 */
548 function load_list(file) {
549         var sys = sys_init();
550         var s = sys.file_load(file);
551         var a = split("\n", s);
552         return a;
553 }
554
555 /* get the rootDSE */
556 var res = ldb.search("", "", ldb.SCOPE_BASE);
557 rootDse = res[0];
558
559 /* load the list of classes we are interested in */
560 var classes = load_list(classfile);
561 var i;
562 for (i=0;i<classes.length;i++) {
563         var classname = classes[i];
564         var class = build_objectclass(ldb, classname);
565         if (class != undefined) {
566                 objectclasses[classname] = class;
567         }
568 }
569
570
571 /*
572   expand the objectclass list as needed
573 */
574 for (i in objectclasses) {
575         expand_objectclass(ldb, objectclasses[i]);
576 }
577
578 /*
579   find objectclass properties
580 */
581 for (i in objectclasses) {
582         find_objectclass_properties(ldb, objectclasses[i]);
583 }
584
585 /*
586   trim the 'may' attribute lists to those really needed
587 */
588 for (i in objectclasses) {
589         trim_objectclass_attributes(ldb, objectclasses[i]);
590 }
591
592 /*
593   form the full list of attributes
594 */
595 for (i in objectclasses) {
596         add_objectclass_attributes(ldb, objectclasses[i]);
597 }
598
599 /* and attribute properties */
600 for (i in attributes) {
601         find_attribute_properties(ldb, attributes[i]);
602 }
603
604 /*
605   dump an ldif form of the attributes and objectclasses
606 */
607 write_ldif(attributes, attrib_attrs);
608 write_ldif(objectclasses, class_attrs);
609
610 write_aggregate();
611
612 if (verbose == undefined) {
613         exit(0);
614 }
615
616 /*
617   dump list of objectclasses
618 */
619 printf("objectClasses:\n")
620 for (i in objectclasses) {
621         printf("\t%s\n", i);
622 }
623 printf("attributes:\n")
624 for (i in attributes) {
625         printf("\t%s\n", i);
626 }
627
628 printf("autocreated attributes:\n");
629 for (i in attributes) {
630         if (attributes[i].autocreate == true) {
631                 printf("\t%s\n", i);
632         }
633 }
634
635 return 0;