//////////////////////////////////////////////////////////////////////////////// // // 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 ControlEndpointRef; [CustomEndpoint] public readonly TRef 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! clients = new ESet(); readonly Hashtable/**/! _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 }; /// /// 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 < * * * * >. The _WildcardMasks /// array controls the search order. /// 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() {} } /// /// 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. /// abstract class AuthenticationProtocol { public abstract bool CreateSupplicant([Claims]ServiceContract.Exp! exp, Credentials! credentials, out CredError error); } }