/// <summary> /// Adds a schema class. /// </summary> /// <param name="obj">The object of model.</param> void AddClass(ModelObject obj) { string governsId = (string)obj[StandardNames.governsId]; string displayName = ((string)obj[StandardNames.ldapDisplayName]).ToLower(); Checks.IsTrue(!classMap.ContainsKey(displayName), "class '{0}' must not be declared twice", displayName); classMap[displayName] = obj; Checks.IsTrue( !classGovernsIdToDisplayNameMap.ContainsKey(governsId), "class '{0}' has ambigious id '{1}'", displayName, governsId); classGovernsIdToDisplayNameMap[governsId] = displayName; // Add object class if not defined by schema if (obj[StandardNames.objectClass] == null) { obj[StandardNames.objectClass] =new AttributeContext( this, StandardNames.objectClass, Syntax.StringObjectIdentifier, false, null).Parse(StandardNames.top + ";" + StandardNames.classSchema); } }
/// <summary> /// Adds a set of object definitions to the schema. The format of the schema /// is a sequence of lines where objects are separated by empty lines. /// Each line of an object definition is a pair of an attribute display name and an /// attribute value, separated by ':'. Takes care of "bootstrapping" the contents of /// the schema, resolving cyclic dependencies between attribute definitions and objects. /// </summary> /// <param name="schema">The schema, represented as explained above.</param> /// <param name="valueSubstitution">A substitution of strings which should be applied to each value in the /// schema. For example, may map '<rootdomaindn>' as used in schemas to represent the root name of the /// current DC to an actual value</param> /// <param name="serverVersion">Specify the OS version of the server</param> /// <returns>Result of the Load Schema method.</returns> public ModelResult LoadSchema(IEnumerable<string> schema, Map<string, string> valueSubstitution, OSVersion serverVersion) { SequenceContainer<ModelObject> newObjects = new SequenceContainer<ModelObject>(); UnresolvedSyntax unresolvedSyntax = new UnresolvedSyntax(); #region Pass 1: read in objects resolving only builtin attributes // We need this phase for bootstrapping, as we don't have the // attributes available which are used in the objects. Thus // in this phase, we just parse attribute values of a certain set // of builting attributes which are required to get attribute syntax // definitions. InitializeBuiltinAttributes(); ModelObject tempobj = null; ModelObject obj = null; foreach (string rawLine in schema) { string line = (rawLine == null)?null:rawLine.Trim(); if (String.IsNullOrEmpty(line)) { // Object ends here if (obj != null) { if (obj[StandardNames.attributeID] == null && obj[StandardNames.governsId] == null) { if (obj.attributes.Count == 1) { // If the server is Windows 2000 if (serverVersion == OSVersion.WinSvr2000) { string attr = obj.attributes.Keys.ElementAt(0); if (tempobj.attributes.Keys.Contains(attr)) { tempobj.attributes.Keys.Remove(attr); tempobj.attributes.Add(obj.attributes.ElementAt(0)); } else { tempobj.attributes.Add(obj.attributes.ElementAt(0)); } newObjects.RemoveAt(newObjects.Count - 1); newObjects.Add(tempobj); } } else { Checks.Fail("attributeId/governsId is mandatory for each object {0}.", obj[StandardNames.cn]); } } else if (obj[StandardNames.attributeID] != null) { AddAttribute(obj); newObjects.Add(obj); } else if (obj[StandardNames.governsId] != null) { // This is a class definition. Fill in class map. AddClass(obj); newObjects.Add(obj); } tempobj = obj; obj = null; } } else { if (obj == null) { obj = new ModelObject(); } obj.dc = this; string attr, valueString; string[] splits = line.Split(new char[] { ':' }, 2); if (splits == null || splits.Length != 2) { Checks.Fail("invalid schema line '{0}'", line); continue; } attr = splits[0].Trim().ToLower(); valueString = Substitute(valueSubstitution, splits[1].Trim()); AttributeContext parsingContext; if (!builtinAttributeSyntax.TryGetValue(attr, out parsingContext)) { parsingContext = new AttributeContext(this, attr, unresolvedSyntax); } obj[attr] = parsingContext.Parse(valueString); } } #endregion #region Pass 2: resolve syntax of all new objects // As we have now attribute definitions which map to the syntax, we can parse // All values in the new objects. foreach (ModelObject newObj in newObjects) { foreach (string attr in newObj.attributes.Keys) { AttributeContext parsingContext = GetAttributeContext(attr); Value value = newObj[attr]; if (value.Syntax is UnresolvedSyntax) { newObj[attr] = parsingContext.Parse((string)value); } else { Checks.IsTrue( parsingContext.syntax == value.Syntax, "bultin syntax assignment of '{0}' must match syntax declared in schema", attr); } } } #endregion #region Pass 3: add objects // Now all definitions are complete. Add them to schema replica root. foreach (ModelObject newObj in newObjects) { AddChild(schemaReplica.root, newObj); } #endregion #region Pass 4: check consistency // The tree is finally setup. Now check for consistency Consistency.Check(newObjects.ToSequence()); #endregion if (Checks.HasDiagnostics) { return new ModelResult(ResultCode.ConstraintViolation, Checks.GetAndClearLog(), Checks.GetAndClearDiagnostics()); } else { //return Result.Success; ModelResult tempRes = new ModelResult(ResultCode.Success); tempRes.logMessage = Checks.GetAndClearLog(); return tempRes; } }
/// <summary> /// Reads a schema from a set of XML files. Returns an enumeration of lines, where each line /// represents an attribute definition with ':' a separator between attribute name and attribute value, /// and where the empty line indicates the end of an object definition. /// </summary> /// <param name="includeFilePatterns">Included file folders</param> /// <param name="excludeFiles">Excluded file folder</param> /// <param name="isProtocolXMLApplied">Set this to true if new protocol XML applied</param> /// <param name="serverVersion">Specify the OS version of the server</param> /// <returns>Returns string array.</returns> public static string[] ReadSchema(string[] includeFilePatterns, string[] excludeFiles, bool isProtocolXMLApplied, OSVersion serverVersion) { bool criticalOnly = false; List <string> result = new List <string>(); Checks.MakeLog("Start loading of XML data..."); if (isProtocolXMLApplied == true) { foreach (string file in Expand(includeFilePatterns, excludeFiles)) { if (serverVersion >= OSVersion.Win2016) { #region raw XMLs used after Windows Server 2016 are changed to openXML type using (WordprocessingDocument wdDoc = WordprocessingDocument.Open(file, false)) { XNamespace w = @"http://schemas.openxmlformats.org/wordprocessingml/2006/main"; XNamespace xml = @"http://www.w3.org/XML/1998/namespace"; var items = wdDoc.MainDocumentPart.Document.Body; XAttribute currentStyle = null; XAttribute previousStyle = null; string previousLine = ""; foreach (var item in items) { if (item is Paragraph) { XElement xe = XElement.Parse(item.OuterXml); currentStyle = xe.Descendants(w + "pPr").Descendants(w + "pStyle").Attributes(w + "val").FirstOrDefault(); if (currentStyle != null && currentStyle.Value.Equals("Code")) { var texts = xe.Descendants(w + "t"); string currentLine = ""; foreach (var text in texts) { currentLine += text.Value; } if (currentLine.Contains("<SchemaNCDN>")) { currentLine = currentLine.Replace("<SchemaNCDN>", "CN=Schema,CN=Configuration,<RootDomainDN>"); } var testsAttr = texts.Attributes(xml + "space").FirstOrDefault(); if (testsAttr != null && testsAttr.Value.Equals("preserve")) { if (currentLine.StartsWith(" ")) { previousLine += currentLine.TrimStart(); } else { if (!string.IsNullOrEmpty(previousLine)) { result.Add(previousLine); } previousLine = currentLine; } } else { if (!string.IsNullOrEmpty(previousLine)) { result.Add(previousLine); } previousLine = currentLine; } } else if (previousStyle != null && previousStyle.Value.Equals("Code")) { if (!string.IsNullOrEmpty(previousLine)) { // If the code does not end with an empty string, add one to seperate it with other schema objects in the result list. result.Add(previousLine); result.Add(string.Empty); } else { // There exists in TD when code already ends up with an empty string. result.Add(previousLine); } previousLine = ""; } previousStyle = currentStyle; } } } #endregion } else { #region raw XMLs used before Windows Server 2012 R2 System.IO.StreamReader sr = new StreamReader(file); string tmp = sr.ReadToEnd(); sr.Close(); //Get data from <snippet> element int index = tmp.IndexOf("</snippet>"); if (serverVersion == OSVersion.WinSvr2008R2) { if (tmp.IndexOf("windows_unreleased") > -1) { if (tmp.IndexOf("</snippet>", tmp.IndexOf("windows_unreleased")) > -1) { string temper = tmp.Substring(tmp.IndexOf("windows_unreleased")); if (temper.ToLower().Contains("cn:") && temper.ToLower().Contains("ldapdisplayname:")) { tmp = temper; index = tmp.IndexOf("</snippet>"); } } } } if (index > -1) { index = tmp.LastIndexOf(">", index) + 1; string sp = tmp.Substring(index, tmp.IndexOf("</snippet>") - index); index = tmp.IndexOf("<snippet"); string spTag = tmp.Substring(index, tmp.IndexOf(">", index) - index + 1); List <string> autoText = new List <string>(); index = 0; //Get platform information while (index < tmp.Length) { int begin = tmp.IndexOf("<auto_text>", index); if (begin > -1) { int end = tmp.IndexOf("</auto_text>", index); if (begin > -1 && end > -1 && end > begin) { string newAdd = tmp.Substring(begin + "<auto_text>".Length, end - begin - "<auto_text>".Length); bool toAdd = true; foreach (string cont in autoText) { if (cont == newAdd) { toAdd = false; break; } } if (toAdd == true) { autoText.Add(newAdd); } index = end + "</auto_text>".Length; } } else { break; } } List <string> implementationSpecific = new List <string>(); foreach (string tagTla in autoText) { if (!tagTla.Equals("windows") && !tagTla.Equals("exchange_server")) { implementationSpecific.Add(tagTla); } } List <string> lines = new List <string>(); bool isCritical = false; string value = sp; value = value.Replace(" ", String.Empty); //Modified. For new XML files, lines are separated by \n, not \n\r string[] splitTag = { "\n" }; string[] tmpValue = value.Split(splitTag, StringSplitOptions.RemoveEmptyEntries); string parsedValue = String.Empty; for (int k = 0; k < tmpValue.Length; k++) { if (!tmpValue[k].Contains(":") || tmpValue[k].IndexOf(":") == 1) { parsedValue = parsedValue + tmpValue[k]; } else { parsedValue = parsedValue + "\r\n" + tmpValue[k]; } } if (parsedValue.Contains("<RootDomainDN>")) { parsedValue = parsedValue.Replace("<RootDomainDN>", "<RootDomainDN>"); } else if (parsedValue.Contains("<SchemaNCDN>")) { parsedValue = parsedValue.Replace("<SchemaNCDN>", "CN=Schema,CN=Configuration,<RootDomainDN>"); } string[] splitTag2 = { "\r\n" }; tmpValue = parsedValue.Split(splitTag2, StringSplitOptions.RemoveEmptyEntries); string savedString = String.Empty; foreach (string line in tmpValue) { if (line.Length > 0) { if (line[line.Length - 1] != ':') { lines.Add(savedString + line); if (criticalOnly && !isCritical) { isCritical = CheckCritical(line); } savedString = String.Empty; } else { savedString = line; } } } if (!criticalOnly || isCritical) { result.AddRange(lines); result.Add(String.Empty); //separate records by an empty element } } #endregion } } } else { foreach (string file in Expand(includeFilePatterns, excludeFiles)) { XmlTextReader reader = new XmlTextReader(file); reader.XmlResolver = null; reader.DtdProcessing = DtdProcessing.Prohibit; XPathDocument doc = new XPathDocument(reader); XPathNavigator node = doc.CreateNavigator(); // The TD XML has no formal tag for representing the object definitions in ADA* and ADSC. // However, it seems that all those definitions are included in an <example> tag which // in turn contains a <snippet> tag with CDATA, and the snippet tag has a type attribute "syntax". // We use this for identifying the object definitions. However, this code may require // changes if the TD XML representation changes, or if it discovered that we include // too many or not enough objects. foreach (XPathNavigator snippet in node.Select("//example/snippet")) { List <string> implementationSpecific = new List <string>(); foreach (XPathNavigator tagTla in node.Select("//p/tla")) { if ( !tagTla.GetAttribute("rid", String.Empty).Equals("windows") && !tagTla.GetAttribute("rid", String.Empty).Equals("exchange_server")) { implementationSpecific.Add(tagTla.GetAttribute("rid", String.Empty)); } } if ( serverVersion != OSVersion.WinSvr2008R2 && (implementationSpecific.Count == 1 && (implementationSpecific.Contains("windows_server_7")))) { continue; } List <string> lines = new List <string>(); bool isCritical = false; string value = snippet.Value; int i = 0; while (i >= 0) { string line = ExtractLine(value, ref i).Trim(); while (i >= 0 && Char.IsWhiteSpace(value[i])) { line += ExtractLine(value, ref i).Trim(); } if (line != String.Empty) { lines.Add(line); if (criticalOnly && !isCritical) { isCritical = CheckCritical(line); } } } if (!criticalOnly || isCritical) { result.AddRange(lines); result.Add(String.Empty); } } } } Checks.MakeLog("Successfully loaded XML data files..."); return(result.ToArray()); }
/// <summary> /// Content rules determine the mandatory and optional attributes of the class instances that are stored /// in the directory. /// </summary> /// <param name="obj">The object of model.</param> public static void CheckContent(ModelObject obj) { List <string> mustContain = new List <string>(); List <string> mayContain = new List <string>(); string[] ExcludeAttributes = { "instancetype", "ntsecuritydescriptor", "objectcategory", "objectsid" }; // Compute sets of may/must attribute string className = (string)obj[StandardNames.objectClass].UnderlyingValues.Last(); List <string> superClassList = GetSuperClassList(obj.dc, className); foreach (string classId in superClassList) { ModelObject classObj; if (!obj.dc.TryGetClass(classId, out classObj)) { // will be reported in a different checker continue; } mustContain.AddRange(GetAttributeSet(classObj, StandardNames.mustContain)); mustContain.AddRange(GetAttributeSet(classObj, StandardNames.systemMustContain)); mayContain.AddRange(GetAttributeSet(classObj, StandardNames.mayContain)); mayContain.AddRange(GetAttributeSet(classObj, StandardNames.systemMayContain)); } //Removing default excluded attributes from this list. foreach (string attr in ExcludeAttributes) { if (mustContain.Contains(attr)) { mustContain.Remove(attr); } } // Check for all attributes string dn = (string)obj[StandardNames.shortName]; //For each attribute foreach (string attr in obj.attributes.Keys) { if (!ExcludeAttributes.Contains(attr)) { //This attribute is in must contain list. if (mustContain.Contains(attr)) { mustContain.Remove(attr); } //This attribute is not in must and may contian list. else if (!mayContain.Contains(attr)) { Checks.Fail("'{0}' must not contain attribute '{1}'", dn, attr); } } } //If must contain list is not empty, this object does not contain some must contain attributes. if (mustContain.Count > 0) { Checks.Fail( "'{0}' does not contain required attributes '{1}'", dn, String.Join(",", mustContain.ToArray())); } }
/// <summary> /// Check objectClass. /// </summary> /// <param name="obj">ModelObject.</param> public static void CheckObjectClass(ModelObject obj) { string dn = (string)obj[StandardNames.shortName]; Value objectClass = obj[StandardNames.objectClass]; if (objectClass == null) { Checks.Fail("'{0}' must have object class", dn); return; } // Check for resolution and last/first element compliance int count = 0; List <string> classes = obj.GetAllClassIds().ToList(); bool resolveOk = true; foreach (string classId in classes) { ModelObject classObject; //The object must be present in Schema NC. if (!obj.dc.TryGetClass(classId, out classObject)) { Checks.Fail("'{0}' has undefined object class '{1}'", dn, classId); resolveOk = false; continue; } //Checking for the first class is Top if (count == 0 && classId != StandardNames.top) { Checks.Fail("'{0}' first object class must be top", dn); resolveOk = false; } //Checking for the last class is Structural class. if (count == objectClass.UnderlyingValues.Count - 1) { ObjectClassCategory category = (ObjectClassCategory)(int)classObject[StandardNames.objectClassCategory]; if (category != ObjectClassCategory.StructuralClass) { Checks.Fail("'{0}' last object class must be structural"); resolveOk = false; } } count++; } if (!resolveOk) { return; } // Check for superclass chaining // Checks that if the value of object class is [top,ac_1,...,ac_n,sc_1,...,sc_n], // then sc_n is the most specific structural class, sc_1...scn_(n-1) is the super class chain of sc_n (excluding top), // and ac_n is the next auxilary class before that, where ac_1...acn(n-1) is that classes chain excluding classes // which have been seen already before (where there can be a number of ac anchors here). List <string> includedClasses = new List <string>(); int i = classes.Count - 1; while (i > 0) { string classId = classes[i--]; if (includedClasses.Contains(classId)) { Checks.Fail("'{0}' object class contains repeated entries", dn); break; } includedClasses.Add(classId); ObjectClassCategory category = (ObjectClassCategory)(int)obj.dc.GetClass(classId)[StandardNames.objectClassCategory]; if (i == classes.Count - 2) { // Most specific class must be structural if (category != ObjectClassCategory.StructuralClass) { Checks.Fail("'{0}' object class most specific class must be structural", dn); } } //If the server is Pre Windows 2003. foreach (string clId in GetSuperClassChain(obj.dc, classId)) { if (includedClasses.Contains(clId)) { // Already included in a previous chain continue; } includedClasses.Add(clId); if (i <= 0) { Checks.Fail( "'{0}' object class does not contain chain of super classes of '{1}' (classes missing)", dn, classId); break; } if (classes[i--] != clId) { Checks.Fail( "'{0}' object class does not contain chain of super classes '{1}' (found unexpected '{2}')", dn, classId, classes[i + 1]); break; } } } }
/// <summary> /// Inheritance is the ability to build new classes from existing classes. /// </summary> /// <param name="obj">The object of model.</param> public static void CheckInheritance(ModelObject obj) { string dn = (string)obj[StandardNames.shortName]; //Get super class id. string superClassId = (string)obj[StandardNames.subClassOf]; if (superClassId == null) { Checks.Fail("'{0}' must have a super class", dn); } else { ModelObject superClass; if (!obj.dc.TryGetClass(superClassId, out superClass)) { Checks.Fail("'{0}' has undefined super class '{1}'", dn, superClassId); } else { string thisClassId = (string)obj[StandardNames.governsId]; if (thisClassId != StandardNames.topGovernsId && thisClassId == superClassId) { Checks.Fail("{0} cannot inherit from itself", dn); } //Get super class and current class category. ObjectClassCategory thisCategory = (ObjectClassCategory)(int)obj[StandardNames.objectClassCategory]; ObjectClassCategory superCategory = (ObjectClassCategory)(int)superClass[StandardNames.objectClassCategory]; bool categoryOk; switch (thisCategory) { //Abstract Class must be inherited from Abstract Class. case ObjectClassCategory.AbstractClass: categoryOk = superCategory == ObjectClassCategory.AbstractClass; break; //Auxiliary Class must not be inherited from Structural Class case ObjectClassCategory.AuxiliaryClass: categoryOk = superCategory != ObjectClassCategory.StructuralClass; break; //Structural Class must not be inherited from Auxiliary Class case ObjectClassCategory.StructuralClass: categoryOk = superCategory != ObjectClassCategory.AuxiliaryClass; break; //Default. default: categoryOk = true; break; } if (!categoryOk) { Checks.Fail("{0} must have object class category which is compatible with its super class", dn); } } } }
/// <summary> /// If it is a structural object class, then the objectClass attribute of objects of the class does not contain /// the name of the auxiliary class aux. /// </summary> /// <param name="obj">The object of model.</param> public static void CheckStructure(ModelObject obj) { //Collect possSuperiors. List <object> possSuperiors = new List <object>(); //Get current class name. string className = obj[StandardNames.ldapDisplayName].UnderlyingValues.ElementAt(0).ToString(); //Get super class list. List <string> superClassList = GetSuperClassList(obj.dc, className); //For each super class foreach (string superClass in superClassList) { //Get class object of this class name. ModelObject classObject = obj.dc.GetClass(superClass); //Get possSuperiors. if (classObject[StandardNames.possSuperiors] != null) { possSuperiors.AddRange(classObject[StandardNames.possSuperiors].UnderlyingValues); } //Get systemPossSuperiors. if (classObject[StandardNames.systemPossSuperiors] != null) { possSuperiors.AddRange(classObject[StandardNames.systemPossSuperiors].UnderlyingValues); } } //For each poss superior class name... foreach (string possSuperior in possSuperiors) { #region two //Get the poss superior object and possibleInferiors attribute. ModelObject superiorObj = obj.dc.GetClass(possSuperior); ModelObject attrObject = superiorObj.dc.GetAttribute(StandardNames.possibleInferiors); if (superiorObj != null) { AttributeContext parsingContext = obj.dc.GetAttributeContext(StandardNames.possibleInferiors); //If this class alrady contains the possibleInferiors... if (superiorObj[StandardNames.possibleInferiors] != null) { string valueString = String.Empty; foreach (object val in superiorObj[StandardNames.possibleInferiors].UnderlyingValues) { valueString = valueString + val.ToString() + ";"; } //Append the current class name with already existing possibleInferiors attribute. valueString = valueString + className; superiorObj[StandardNames.possibleInferiors] = parsingContext.Parse(valueString); } else { //Create a new possibleInferiors attribute with the cureent class name. superiorObj[StandardNames.possibleInferiors] = parsingContext.Parse(className); } } else { Checks.Fail( "{0} class's possSuperior {1} is not in Schema NC.", className, superiorObj.attributes[StandardNames.cn].UnderlyingValues.ElementAt(0).ToString()); } #endregion } }
/// <summary> /// Adds an object to DC at parent location. /// The objects content is specified by a sequence of strings where each string /// consists of a pair of attribute name and value. /// </summary> /// <param name="parentDN">The DN of parent.</param> /// <param name="attributes">All attributes of model object.</param> public ModelResult Add(string parentDN, params string[] attributes) { ModelObject parent = TryFindObject(parentDN); if (parent == null) { return(new ModelResult(ResultCode.NoSuchObject, "cannot find '{0}'", parentDN)); } ModelObject obj = new ModelObject(); obj.dc = this; foreach (string line in attributes) { string attr, valueString; string[] splits = line.Split(new char[] { ':' }, 2); if (splits == null || splits.Length != 2) { return(new ModelResult(ResultCode.InvalidAttributeSyntax)); } attr = splits[0].Trim(); valueString = splits[1].Trim(); ModelObject attrObj; if (!TryGetAttribute(splits[0], out attrObj)) { return(new ModelResult(ResultCode.NoSuchAttribute, attr)); } AttributeContext parsingContext = GetAttributeContext(attr); obj[attr] = parsingContext.Parse(valueString); if (Checks.HasDiagnostics) { return(new ModelResult(ResultCode.InvalidAttributeSyntax, Checks.GetAndClearDiagnostics())); } } string rdnName = GetRDNAttributeName(obj); if (rdnName != null) { string rdn = (string)obj[rdnName]; if (parent.childs.ContainsKey(rdn)) { return(new ModelResult(ResultCode.EntryAlreadyExists)); } AddChild(parent, obj); Consistency.Check(new List <ModelObject> { obj }); } if (Checks.HasDiagnostics) { return(new ModelResult(ResultCode.ConstraintViolation, Checks.GetAndClearDiagnostics())); } else { return(ModelResult.Success); } }