singrdk/base/Services/CredentialsManager/CredentialsManagerService.sg

654 lines
30 KiB
Plaintext

////////////////////////////////////////////////////////////////////////////////
//
// Microsoft Research Singularity
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: Services/CredentialsManager/CredentialsManagerService.sg
//
// Note: Contains the entry point and main I/O loop for the service.
//
// All clients that connect using the CredentialsManagerContract are
// serviced on the "main" thread of the CM. All of the requests of
// that contract can be satisfied synchronously (without waiting for
// any external I/O or blocking).
//
// Supplicant channels are serviced on separate threads. Although it
// would be possible to service some of them on the main thread,
// separate threads make the code simpler, and allow for later protocols
// which might require more serious work be done to service requests.
//
using System;
using System.Collections;
using System.Diagnostics;
using Microsoft.Contracts;
using Microsoft.Singularity;
using Microsoft.Singularity.Configuration;
using Microsoft.Singularity.Channels;
using Microsoft.Singularity.Directory;
using Microsoft.Singularity.Io;
using Microsoft.Singularity.ServiceManager;
using Microsoft.SingSharp;
using Ex = Microsoft.Singularity.Security;
namespace Microsoft.Singularity.Security.CredentialsManager
{
[Category("Service")]
internal class ServiceParameters
{
[CustomEndpoint]
public readonly TRef<ServiceProcessContract.Exp:Starting> ControlEndpointRef;
[CustomEndpoint]
public readonly TRef<ServiceEventContract.Imp:Ready> EventEndpointRef;
reflective private ServiceParameters();
}
internal class CredentialsManagerService : ITracked
{
internal static int AppMain(ServiceParameters! parameters)
{
assert parameters.ControlEndpointRef != null;
assert parameters.EventEndpointRef != null;
ServiceProcessContract.Exp:Starting! svcontrol = parameters.ControlEndpointRef.Acquire();
ServiceEventContract.Imp:Ready! svevent = parameters.EventEndpointRef.Acquire();
CredentialsManagerService service = new CredentialsManagerService(svcontrol, svevent);
try {
service.Run();
return 0;
}
finally {
service.Dispose();
}
}
#region Instance Fields
readonly ServiceProcessContract.Exp! svmanager;
readonly ServiceEventContract.Imp! svevent;
readonly CredentialsCollection! _credentials = new CredentialsCollection();
readonly ProtocolMappingCollection! _protocolMappings = new ProtocolMappingCollection();
readonly ESet<CredentialsManagerContract.Exp:Ready>! clients = new ESet<CredentialsManagerContract.Exp:Ready>();
readonly Hashtable/*<string,AuthenticationProtocol>*/! _authenticationProtocols = new Hashtable();
#endregion
CredentialsManagerService(
[Claims]ServiceProcessContract.Exp:Starting! svcontrol,
[Claims]ServiceEventContract.Imp:Ready! svevent)
{
this.svmanager = svcontrol;
this.svevent = svevent;
}
public void Dispose()
{
delete svmanager;
delete svevent;
clients.Dispose();
}
void Run()
{
_authenticationProtocols[AuthenticationProtocolNames.Ntlm] = new NtlmAuthenticationProtocol();
expose(this) {
svmanager.SendStartSucceeded();
for (;;) {
try {
switch receive {
case svmanager.Connect(char[] in ExHeap expath, ServiceContract.Exp:Start! exp):
{
if (expath != null) {
string! path = Bitter.ToString2(expath);
DebugLine("Received 'Connect' from Service Manager, for subpath '{0}'.", path);
DebugLine("This service does not support subpaths, so the connect request will be rejected.");
delete expath;
delete exp;
svmanager.SendNakConnect(ErrorCode.NotFound, null);
}
else {
CredentialsManagerContract.Exp:Start client = exp as CredentialsManagerContract.Exp:Start;
if (client != null) {
DebugLine("Received 'Connect' from Service Manager, using CredentialsManagerContract.");
client.SendSuccess();
clients.Add(client);
svmanager.SendAckConnect();
}
else {
DebugLine("Received 'Connect' from Service Manager, but the contract is not recognized.");
delete exp;
svmanager.SendNakConnect(ErrorCode.ContractNotSupported, null);
}
}
break;
}
case svmanager.ChannelClosed():
DebugLine("Service manager has closed the managed service channel. Not good. Terminating.");
return;
case svmanager.Knock():
DebugLine("Received Knock from Service Manager. Answering with Alive.");
svmanager.SendAlive();
break;
case svmanager.Stop():
DebugLine("Received 'Stop' from Service Manager. Stopping.");
svmanager.SendAckStop();
return;
case client.ChannelClosed() in clients:
// DebugLine("Client has disconnected.");
delete client;
break;
case client.AddCredentials(Ex.CredentialsId excredentials, char[]! in ExHeap expassword, bool replace) in clients:
{
CredentialsId id = new CredentialsId(
Bitter.ToString2(excredentials.CredentialsName),
Bitter.ToString2(excredentials.Tag));
string! password = Bitter.ToString2(expassword);
excredentials.Dispose();
delete expassword;
if (TraceSwitches.ShowManagerMessages) {
DebugLine("Received AddCredentials: " + id + " [evidence not shown]");
}
if (_credentials.ContainsKey(id) && !replace) {
if (TraceSwitches.ShowManagerMessages)
DebugLine(" Already have credentials matching that key.");
client.SendRequestFailed(CredError.MatchingEntryExists);
}
else {
if (TraceSwitches.ShowManagerMessages)
DebugLine(" Setting credentials.");
PasswordEvidence evidence = new PasswordEvidence(password);
Credentials credentials = new Credentials(id, evidence);
_credentials[id] = credentials;
client.SendOk();
}
clients.Add(client);
break;
}
case client.DeleteCredentials(excredentials) in clients:
{
CredentialsId id = new CredentialsId(
Bitter.ToString2(excredentials.CredentialsName),
Bitter.ToString2(excredentials.Tag));
excredentials.Dispose();
if (TraceSwitches.ShowManagerMessages)
DebugLine("Received DeleteCredentials: " + id.ToString());
if (_credentials.ContainsKey(id)) {
if (TraceSwitches.ShowManagerMessages)
DebugLine(" Removing credentials.");
_credentials.Remove(id);
client.SendOk();
}
else {
if (TraceSwitches.ShowManagerMessages)
DebugLine(" No such credentials registered.");
client.SendRequestFailed(CredError.NoMatchingCredentials);
}
clients.Add(client);
break;
}
case client.DeleteAllCredentials() in clients:
{
if (TraceSwitches.ShowManagerMessages)
DebugLine("Received DeleteAllCredentials");
_credentials.Clear();
client.SendOk();
clients.Add(client);
break;
}
case client.EnumerateCredentials() in clients:
{
if (TraceSwitches.ShowManagerMessages)
DebugLine("Received EnumerateCredentials");
ICollection! keys = (!)_credentials.Keys;
int count = keys.Count;
if (TraceSwitches.ShowManagerMessages)
DebugLine(" Sending " + count + " entries");
Ex.CredentialsId[]! in ExHeap list = new[ExHeap] Ex.CredentialsId[count];
IEnumerator enumerator = keys.GetEnumerator();
for (int i = 0; i < count; i++) {
if (!enumerator.MoveNext()) {
if (TraceSwitches.ShowManagerMessages)
DebugLine(" Enumerator lied to us!");
break;
}
CredentialsId! id = (!)(CredentialsId)enumerator.Current;
if (TraceSwitches.ShowManagerMessages)
DebugLine(" Entry: " + id.ToString());
expose(list[i]) {
// these will be null, but we have to keep the type checker happy
delete list[i].CredentialsName;
delete list[i].Tag;
list[i].CredentialsName = Bitter.FromString2(id.CredentialsName);
list[i].Tag = Bitter.FromString2(id.Tag);
}
}
client.SendCredentialsList(list);
clients.Add(client);
break;
}
case client.AddProtocolMapping(Ex.ProtocolTuple extuple, Ex.CredentialsId excredentials, bool replace) in clients:
{
ProtocolTuple tuple = new ProtocolTuple(extuple);
CredentialsId credentials = new CredentialsId(excredentials);
extuple.Dispose();
excredentials.Dispose();
if (TraceSwitches.ShowManagerMessages)
DebugLine("Received AddProtocolMapping: {0} -> {1}", tuple.ToString(), credentials.ToString());
if (!replace && _protocolMappings.ContainsKey(tuple)) {
if (TraceSwitches.ShowManagerMessages)
DebugLine("A protocol mapping with the same key already exists.");
client.SendRequestFailed(CredError.MatchingEntryExists);
clients.Add(client);
break;
}
if (!_credentials.ContainsKey(credentials)) {
if (TraceSwitches.ShowManagerMessages)
DebugLine("The specified credentials do not exist.");
client.SendRequestFailed(CredError.NoMatchingCredentials);
clients.Add(client);
break;
}
_protocolMappings[tuple] = credentials;
client.SendOk();
clients.Add(client);
break;
}
case client.DeleteProtocolMapping(Ex.ProtocolTuple extuple) in clients:
ProtocolTuple tuple = new ProtocolTuple(extuple);
extuple.Dispose();
if (TraceSwitches.ShowManagerMessages)
DebugLine("Received DeleteProtocolMapping: tuple = " + tuple.ToString());
if (_protocolMappings.ContainsKey(tuple)) {
if (TraceSwitches.ShowManagerMessages)
DebugLine(" Protocol mapping removed.");
_protocolMappings.Remove(tuple);
client.SendOk();
}
else {
if (TraceSwitches.ShowManagerMessages)
DebugLine(" No matching entry");
client.SendRequestFailed(CredError.NoMatchingCredentials);
}
clients.Add(client);
break;
case client.DeleteAllProtocolMappings() in clients:
{
if (TraceSwitches.ShowManagerMessages)
DebugLine("Received DeleteAllProtocolMappings");
_protocolMappings.Clear();
client.SendOk();
clients.Add(client);
break;
}
case client.FindMatchingProtocolMapping(Ex.ProtocolTuple extuple, bool useWildcards) in clients:
{
if (TraceSwitches.ShowManagerMessages)
DebugLine("Received FindMatchingProtocolMapping:");
ProtocolTuple tuple = new ProtocolTuple(extuple);
extuple.Dispose();
CredentialsId match;
if (FindMatchingProtocolMapping(tuple, out match, useWildcards)) {
assert match != null;
DebugLine(" Match found: " + match.ToString());
client.SendMatchingProtocolMapping(match.ToExchange());
}
else {
DebugLine(" No matching entry found.");
client.SendRequestFailed(CredError.NoMatchingCredentials);
}
clients.Add(client);
break;
}
case client.EnumerateProtocolMappings() in clients:
{
if (TraceSwitches.ShowManagerMessages)
DebugLine("Received EnumerateProtocolMappings");
int count = _protocolMappings.Count;
Ex.ProtocolMapping[]! in ExHeap list = new[ExHeap] Ex.ProtocolMapping[count];
IEnumerator enumerator = _protocolMappings.GetEnumerator();
for (int i = 0; i < count; i++) {
if (!enumerator.MoveNext()) {
DebugLine("Enumerator lied!!");
break;
}
DictionaryEntry entry = (DictionaryEntry)(!)enumerator.Current;
ProtocolTuple! tuple = (ProtocolTuple)entry.Key;
CredentialsId! id = (!)(CredentialsId)entry.Value;
expose (list[i]) {
list[i].Dispose();
list[i].ProtocolTuple.ApplicationProtocol = Bitter.FromString2(tuple.ApplicationProtocol);
list[i].ProtocolTuple.AuthenticationProtocol = Bitter.FromString2(tuple.AuthenticationProtocol);
list[i].ProtocolTuple.ServiceAddress = Bitter.FromString2(tuple.ServiceAddress);
list[i].ProtocolTuple.Realm = Bitter.FromString2(tuple.Realm);
list[i].CredentialsId.CredentialsName = Bitter.FromString2(id.CredentialsName);
list[i].CredentialsId.Tag = Bitter.FromString2(id.Tag);
}
}
client.SendProtocolMappings(list);
clients.Add(client);
break;
}
case client.CreateSupplicant(
char[]! in ExHeap exauthenticationProtocol,
Ex.CredentialsId excredentials,
ServiceContract.Exp:Start! exp) in clients:
{
if (TraceSwitches.ShowManagerMessages)
DebugLine("Received CreateSupplicant");
string! authenticationProtocol = Util.ToStringDelete(exauthenticationProtocol);
CredentialsId! id = new CredentialsId(excredentials);
excredentials.Dispose();
if (TraceSwitches.ShowManagerMessages)
DebugLine(" Credentials ID: " + id);
// First, find credentials.
Credentials credentials;
if (!_credentials.TryGetValue(id, out credentials)) {
if (TraceSwitches.ShowManagerMessages)
DebugLine(" Credentials not found.");
delete exp;
client.SendRequestFailed(CredError.NoMatchingCredentials);
clients.Add(client);
break;
}
assert credentials != null;
// For now, we support only a hard-coded set of authentication protocols.
AuthenticationProtocol protocol = (AuthenticationProtocol)_authenticationProtocols[authenticationProtocol];
if (protocol == null) {
if (TraceSwitches.ShowManagerMessages)
DebugLine(" Authentication protocol not recognized: " + authenticationProtocol);
client.SendRequestFailed(CredError.NoMatchingAuthenticationProtocol);
delete exp;
clients.Add(client);
break;
}
CredError error;
if (protocol.CreateSupplicant(exp, credentials, out error)) {
if (TraceSwitches.ShowManagerMessages)
DebugLine(" Authentication protocol accepted client");
client.SendOk();
}
else {
if (TraceSwitches.ShowManagerMessages)
DebugLine(" Authentication protocol refused client: " + error);
client.SendRequestFailed(error);
}
clients.Add(client);
break;
}
case client.CreateSupplicantForProtocol(Ex.ProtocolTuple extuple, ServiceContract.Exp:Start! exp) in clients:
{
ProtocolTuple tuple = new ProtocolTuple(extuple);
extuple.Dispose();
if (TraceSwitches.ShowManagerMessages) {
DebugLine("Received CreateSupplicantForProtocol");
DebugLine(" Tuple = " + tuple);
}
// First, find credentials.
CredentialsId id;
if (!FindMatchingProtocolMapping(tuple, out id, true)) {
if (TraceSwitches.ShowManagerMessages)
DebugLine(" No matching credentials found.");
delete exp;
client.SendRequestFailed(CredError.NoMatchingCredentials);
clients.Add(client);
break;
}
assert id != null;
if (TraceSwitches.ShowManagerMessages)
DebugLine(" Selected credentials: " + id);
Credentials credentials;
if (!_credentials.TryGetValue(id, out credentials)) {
if (TraceSwitches.ShowManagerMessages)
DebugLine(" Credentials not found.");
delete exp;
client.SendRequestFailed(CredError.NoMatchingCredentials);
clients.Add(client);
break;
}
assert credentials != null;
AuthenticationProtocol protocol = (AuthenticationProtocol)_authenticationProtocols[tuple.AuthenticationProtocol];
if (protocol == null) {
if (TraceSwitches.ShowManagerMessages)
DebugLine(" Authentication protocol not recognized: " + tuple.AuthenticationProtocol);
client.SendRequestFailed(CredError.NoMatchingAuthenticationProtocol);
delete exp;
clients.Add(client);
break;
}
CredError error;
if (protocol.CreateSupplicant(exp, credentials, out error)) {
if (TraceSwitches.ShowManagerMessages)
DebugLine(" Authentication protocol accepted client");
client.SendAckCreateSupplicantForProtocol(id.ToExchange());
}
else {
if (TraceSwitches.ShowManagerMessages)
DebugLine(" Authentication protocol refused client: " + error);
client.SendRequestFailed(error);
}
clients.Add(client);
break;
}
}
}
catch (Exception ex) {
DebugLine("Uncaught exception in switch-receive loop!");
Util.DumpException(ex);
DebugLine("Will soldier on...");
}
}
}
}
// This is a permutation on 4 bits, sorted in ascending order
// of the number of bits that are set to 1. We use this to order
// our search of the protocol mappings table. Each bit in the mask
// that is set to 1 indicates a field in ProtocolTuple to replace
// with a wildcard.
static readonly int[]! _WildcardMasks = {
0, // 0000 // 0 bits set
1, // 0001 // 1 bit set
2, // 0010
4, // 0100
8, // 1000
3, // 0011 // groups with 2 bits set
5, // 0101
6, // 0110
9, // 1001
10, // 1010
12, // 1100
7, // 0111 // 3 bits set
11, // 1011
13, // 1101
14, // 1110
15, // 1111 // 4 bits set
};
/// <summary>
/// This class searches the protocol mappings table for credentials that match a given
/// protocol tuple. If the useWildcard argument is false, then a single, exact match
/// is performed. If the useWildcard argument is true, then the method will search for
/// the most specific protocol mapping that matches the tuple provided. If an exact
/// search does not find a tuple, then the method will replace some of the fields with
/// the wildcard marker ("*") and try again, until it has tried all combinations of
/// fields, including the "all fields wild" tuple of &lt; * * * * &gt;. The _WildcardMasks
/// array controls the search order.
///</summary>
bool FindMatchingProtocolMapping(ProtocolTuple! tuple, out CredentialsId match, bool useWildcards)
{
if (useWildcards) {
if (TraceSwitches.ShowWildcardMatching) {
DebugLine(" Query is a wildcard query");
DebugLine("Generating variations of this tuple: " + tuple);
}
match = null;
// bit 0:
for (int permutation_index = 0; permutation_index < _WildcardMasks.Length; permutation_index++) {
int wildcard_mask = _WildcardMasks[permutation_index];
// If the tuple that we're using as our search key
// Each bit in 'wildcard_mask' corresponds to a field in ProtocolTuple.
// Generate a search tuple (variation) with some values replaced with the wildcard marker "*".
// Then query the table for the variation.
ProtocolTuple variation = new ProtocolTuple(
((wildcard_mask & (1 << ProtocolTuple.ApplicationProtocolMaskBit)) != 0) ? ProtocolTuple.WildcardValue : tuple.ApplicationProtocol,
((wildcard_mask & (1 << ProtocolTuple.ServiceAddressMaskBit)) != 0) ? ProtocolTuple.WildcardValue : tuple.ServiceAddress,
((wildcard_mask & (1 << ProtocolTuple.AuthenticationProtocolMaskBit)) != 0) ? ProtocolTuple.WildcardValue : tuple.AuthenticationProtocol,
((wildcard_mask & (1 << ProtocolTuple.RealmMaskBit)) != 0) ? ProtocolTuple.WildcardValue : tuple.Realm);
CredentialsId m;
if (_protocolMappings.TryGetValue(variation, out m)) {
assert m != null;
if (TraceSwitches.ShowWildcardMatching)
DebugLine(" Variation MATCHES: mask: {0:x1} {1} -> {2}", wildcard_mask, variation.ToString(), m);
if (match == null) {
if (TraceSwitches.ShowWildcardMatching)
DebugLine(" (using this match, showing future matches only for debugging)");
match = m;
}
}
else {
if (TraceSwitches.ShowWildcardMatching)
DebugLine(" Variation: no match, mask: {0:x1} {1}", wildcard_mask, variation.ToString());
}
}
if (match != null) {
if (TraceSwitches.ShowWildcardMatching)
DebugLine(" Final match: " + match);
return true;
}
else {
if (TraceSwitches.ShowWildcardMatching)
DebugLine(" No matches.");
return false;
}
}
else {
// The client wants to do a simple, exact match (no wildcards).
// This is easy.
if (TraceSwitches.ShowWildcardMatching)
DebugLine(" Query is an exact-match query (no wildcards)");
return _protocolMappings.TryGetValue(tuple, out match);
}
}
void DebugLine(string! format, params object[]! args)
{
DebugLine(String.Format(format, args));
}
void DebugLine(string! line)
{
DebugStub.WriteLine(line);
}
void ITracked.Acquire() {}
void ITracked.Release() {}
void ITracked.Expose() {}
void ITracked.UnExpose() {}
}
/// <summary>
/// Represents an authentication protocol, but not an instance of such a protocol.
/// Derived classes override the CreateSupplicant method, which is used to create
/// instances of authentication protocols.
/// </summary>
abstract class AuthenticationProtocol
{
public abstract bool CreateSupplicant([Claims]ServiceContract.Exp! exp, Credentials! credentials, out CredError error);
}
}