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