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