// ---------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // ---------------------------------------------------------------------------- // #define VERBOSE // #define REALLY_VERBOSE namespace Microsoft.Singularity.Security.AccessControl { using System; using System.Text; using System.Text.RegularExpressions; using System.Collections; using Microsoft.SingSharp; using Microsoft.Singularity.Security; using Microsoft.Singularity.Channels; /// /// Converts an Access Control List description to a regular expression. /// Caches group expansions for improved performance. /// public class AclConverter { /// /// Regular expression matching an path fragment /// protected const string pathfrag = @"[a-zA-Z1-90_]+(\.[a-zA-Z1-90_]+)*"; /// /// Regular expression matching the beginning of a string /// protected const string start = "^("; /// /// Regular expression matching the end of a string /// protected const string end = ")$"; /// expression /// The reader of group definitions /// protected Cache! cache; private IAclCoreSupport support; /// /// Create new converter, /// private const int ExpnCachePrunePercent = 20; public AclConverter(IAclCoreSupport _support, int expnCacheMaxEntries, int expnCacheExpirySeconds) { this.support = _support; this.cache = new Cache(expnCacheMaxEntries, expnCacheExpirySeconds, ExpnCachePrunePercent, "Expansion Cache: "); } /// /// Convert the specified ACL to a regular expression /// /// The access control list /// An equivalent text regular expression public void DumpStats(StringBuilder! sb) { cache.DumpStats(sb); } public void ClearStats() { cache.ResetStats(); } public void FlushCache() { cache.Prune(true); } public string! Convert(string! aclstr, bool bypassCache, out DateTime minExpiry) { StringBuilder! sb = new StringBuilder(); Stack! stack = new Stack(); int nSubExprs = 0; minExpiry = DateTime.MaxValue; sb.Append(start); DoExpand(aclstr, sb, "", stack, bypassCache, ref nSubExprs, ref minExpiry); sb.Append(end); return sb.ToString(); } public string! ConvertTest(string! aclstr, out int nSubExprs) { DateTime minExpiry = DateTime.MaxValue; StringBuilder! sb = new StringBuilder(); Stack! stack = new Stack(); nSubExprs = 0; sb.Append(start); DoExpand(aclstr, sb, "", stack, false, ref nSubExprs, ref minExpiry); sb.Append(end); return sb.ToString(); } internal class CacheEntry : ICacheValue { public string! expansion; public bool constant; public int nSubExprs; public CacheEntry(Cache! cache, string! expansion, bool constant, int nSubExprs, DateTime minExpiry) { base(cache, minExpiry); this.expansion = expansion; this.constant = constant; this.nSubExprs = nSubExprs; } } private void DoExpand(string! pattern, StringBuilder! sb, string! ns, Stack! stack, bool bypassCache, ref int nSubExprs, ref DateTime minExpiry) { AclToken t; AclParser parser = new AclParser(pattern); CacheEntry ce = null; int eStart = sb.Length; int eSubExprs = nSubExprs; ce = (CacheEntry) cache.GetEntry(pattern); if (ce != null) { if (ce.Expired) ce = null; else if (!bypassCache || ce.constant) { #if VERBOSE DebugStub.Print("DoExpand: Cache hit {0}\n", __arglist(path)); #endif if (ce.expiry < minExpiry) minExpiry = ce.expiry; sb.Append(ce.expansion); nSubExprs += ce.nSubExprs; return; } } while ((t = parser.NextToken()) != null) { switch (t.Type) { case AclTokenType.Literal: #if REALLY_VERBOSE DebugStub.Print("Literal: {0}\n", __arglist(t.Text)); #endif sb.Append(t.Text); break; case AclTokenType.Arc: #if REALLY_VERBOSE DebugStub.Print("Arc: {0}\n", __arglist(t.Text)); #endif // Arcs need no transformation sb.Append(t.Text); break; case AclTokenType.Any: // Any matches any path fragment #if REALLY_VERBOSE DebugStub.Print("Any: {0}\n", __arglist(t.Text)); #endif sb.Append(pathfrag); break; case AclTokenType.Escape: // Reserved regular expression symbol: escape it #if REALLY_VERBOSE DebugStub.Print("Escape: {0}\n", __arglist(t.Text)); #endif sb.AppendFormat("\\{0}", t.Text); break; case AclTokenType.GroupName: // //A sub-expression can have relative or absolute name. //We resolve relative names by keeping track of the namespace //of the last resolution. // bool isPath = true; string path = t.Text; nSubExprs++; if (path.Length != 0) { if (path[0] == '$') { isPath = false; } else { if (path[0] != '/') { // this is a relative path. append the namespace path = ns + "/" + path; } // update the namespace name: int index = path.LastIndexOf('/'); if (index == -1) { ns = String.Empty; } else { ns = (!)path.Substring(0, index); } } string subexpr = ReadSubexpression(path, isPath); if (subexpr == null) { #if VERBOSE DebugStub.Print("Undefined subexpression: {0}\n", __arglist(t.Text)); #endif } if (stack.Contains(path)) { throw new Exception("Infinite recursion detected while expanding acl."); } sb.Append('('); stack.Push(path); if (subexpr != null && subexpr.Length != 0) DoExpand(subexpr, sb, ns, stack, bypassCache, ref nSubExprs, ref minExpiry); stack.Pop(); sb.Append(')'); } break; case AclTokenType.Miscellaneous: DebugStub.Print("Invalid token: {0} Skipping it\n", __arglist(t.Text)); break; } } // cache the results of the expansion at this level string expansion = sb.ToString(eStart, sb.Length-eStart); if (ce == null || expansion != ce.expansion) { ce = new CacheEntry(cache, expansion, false, nSubExprs-eSubExprs, minExpiry); cache.AddEntry(pattern, ce); } else if (ce != null) { // Old expansion was the same, update expiry. ce.ResetExpiry(cache, minExpiry); } if (ce.expiry < minExpiry) minExpiry = ce.expiry; } private string ReadSubexpression(string! path, bool isPath) { string result = null; if (isPath) { if (support == null) DebugStub.Print("ReadSubexpression: AclCoreSupport object missing\n"); else result = support.Expand(path); } else result = Principal.ExpandAclIndirection(path); return result; } // //// this code has been moved to the client impl (e.g. DirAclCoreSupport.sg) // //private string ReadGroup(string! path) //{ // return support.GetGroupContents(path); //} // //// read the file size // DirectoryServiceContract.Imp:Ready! ns = AcquireNSRoot(); // byte[] result = ReadFromFile(path, ns); // ReleaseNSRoot(ns); // // if (result == null) // return null; // // string! subexpression = (!)encoder.GetString(result); // return subexpression; //} // //private DirectoryServiceContract.Imp:Ready! AcquireNSRoot() //{ // // if (cachedRootNS == null) // cachedRootNS = // new TRef(support.GroupDirectoryRoot()); // return cachedRootNS.Acquire(); //} // //private void ReleaseNSRoot([Claims] DirectoryServiceContract.Imp:Ready! ns) //{ // cachedRootNS.Release(ns); //} // //private byte[] ReadFromFile(string!path, DirectoryServiceContract.Imp:Ready! ns) //{ // ErrorCode errCode; // byte[] result; // long size = 0; // int readSize = 4096; // NodeType nodeType; // // // try { // bool temp = SdsUtils.GetAttributes(path, ns, out size, out nodeType, out errCode); // if (!temp) // // element does not exist or an error has occurred // throw new Exception("Cannot get attributes - " + SdsUtils.ErrorCodeToString(errCode)); // // result = new byte[size]; // byte[] in ExHeap buf = new[ExHeap] byte[readSize]; // if (result == null || buf == null) // throw new Exception("Cannot allocate memory"); // // FileContract.Imp! fileImp; // FileContract.Exp! fileExp; // FileContract.NewChannel(out fileImp, out fileExp); // bool bindOk = SdsUtils.Bind(path, ns, fileExp, out errCode); // if (!bindOk) { // delete fileImp; // throw new Exception("Can't read group - " + SdsUtils.ErrorCodeToString(errCode)); // } // fileImp.RecvSuccess(); // // long readOffset = 0; // while (true) { // fileImp.SendRead(buf, 0, readOffset, readSize); // switch receive { // case fileImp.AckRead(localbuf, bytesRead, error) : // // move the memory // Bitter.ToByteArray(localbuf, 0, (int)bytesRead, result, (int)readOffset); // if (bytesRead == readSize) { // // see if there is more // readOffset += bytesRead; // buf = localbuf; // continue; // } // delete localbuf; // break; // case fileImp.ChannelClosed() : // break; // case unsatisfiable : // break; // } // break; // } // delete fileImp; // } // catch (Exception e) { // DebugStub.Print("An exception occurred while reading group definition: {0}\n", // __arglist(path)); // DebugStub.Print("Message: {0}\n", __arglist(e.Message)); // return null; // } // return result; //} // } }