354 lines
13 KiB
Plaintext
354 lines
13 KiB
Plaintext
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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;
|
|
|
|
/// <summary>
|
|
/// Converts an Access Control List description to a regular expression.
|
|
/// Caches group expansions for improved performance.
|
|
/// </summary>
|
|
|
|
public class AclConverter
|
|
{
|
|
/// <summary>
|
|
/// Regular expression matching an path fragment
|
|
/// </summary>
|
|
protected const string pathfrag = @"[a-zA-Z1-90_]+(\.[a-zA-Z1-90_]+)*";
|
|
/// <summary>
|
|
/// Regular expression matching the beginning of a string
|
|
/// </summary>
|
|
protected const string start = "^";
|
|
/// <summary>
|
|
/// Regular expression matching the end of a string
|
|
/// </summary>
|
|
protected const string end = "$";
|
|
|
|
/// <summary>expression
|
|
/// The reader of group definitions
|
|
/// </summary>
|
|
protected Cache! cache;
|
|
private IAclCoreSupport support;
|
|
|
|
/// <summary>
|
|
/// Create new converter,
|
|
/// </summary>
|
|
|
|
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: ");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert the specified ACL to a regular expression
|
|
/// </summary>
|
|
/// <param name="acl">The access control list</param>
|
|
/// <returns>An equivalent text regular expression</returns>
|
|
|
|
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<DirectoryServiceContract.Imp:Ready>(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;
|
|
}
|
|
*/
|
|
|
|
}
|
|
}
|
|
|
|
|