using System; using System.IO; namespace Unosquare.Swan.Networking.Ldap { /// <summary> /// Represents an Ldap Message. /// <pre> /// LdapMessage ::= SEQUENCE { /// messageID MessageID, /// protocolOp CHOICE { /// bindRequest BindRequest, /// bindResponse BindResponse, /// unbindRequest UnbindRequest, /// searchRequest SearchRequest, /// searchResEntry SearchResultEntry, /// searchResDone SearchResultDone, /// searchResRef SearchResultReference, /// modifyRequest ModifyRequest, /// modifyResponse ModifyResponse, /// addRequest AddRequest, /// addResponse AddResponse, /// delRequest DelRequest, /// delResponse DelResponse, /// modDNRequest ModifyDNRequest, /// modDNResponse ModifyDNResponse, /// compareRequest CompareRequest, /// compareResponse CompareResponse, /// abandonRequest AbandonRequest, /// extendedReq ExtendedRequest, /// extendedResp ExtendedResponse }, /// controls [0] Controls OPTIONAL } /// </pre> /// Note: The creation of a MessageID should be hidden within the creation of /// an RfcLdapMessage. The MessageID needs to be in sequence, and has an /// upper and lower limit. There is never a case when a user should be /// able to specify the MessageID for an RfcLdapMessage. The MessageID() /// constructor should be package protected. (So the MessageID value /// isn't arbitrarily run up.). /// </summary> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> internal sealed class RfcLdapMessage : Asn1Sequence { private readonly Asn1Object _op; /// <summary> /// Initializes a new instance of the <see cref="RfcLdapMessage"/> class. /// Create an RfcLdapMessage request from input parameters. /// </summary> /// <param name="op">The op.</param> /// <param name="controls">The controls.</param> public RfcLdapMessage(IRfcRequest op, RfcControls controls) : base(3) { this._op = (Asn1Object)op; this.Add(new RfcMessageID()); // MessageID has static counter this.Add((Asn1Object)op); if(controls != null) { this.Add(controls); } } /// <summary> /// Initializes a new instance of the <see cref="RfcLdapMessage"/> class. /// Will decode an RfcLdapMessage directly from an InputStream. /// </summary> /// <param name="stream">The stream.</param> /// <param name="len">The length.</param> /// <exception cref="Exception">RfcLdapMessage: Invalid tag: " + protocolOpId.Tag.</exception> [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0068:Empfohlenes Dispose-Muster verwenden", Justification = "<Ausstehend>")] public RfcLdapMessage(Stream stream, Int32 len) : base(stream, len) { // Decode implicitly tagged protocol operation from an Asn1Tagged type // to its appropriate application type. Asn1Tagged protocolOp = (Asn1Tagged)this.Get(1); Asn1Identifier protocolOpId = protocolOp.GetIdentifier(); SByte[] content = ((Asn1OctetString)protocolOp.TaggedValue).ByteValue(); MemoryStream bais = new MemoryStream(content.ToByteArray()); switch((LdapOperation)protocolOpId.Tag) { case LdapOperation.SearchResponse: this.Set(1, new RfcSearchResultEntry(bais, content.Length)); break; case LdapOperation.SearchResult: this.Set(1, new RfcSearchResultDone(bais, content.Length)); break; case LdapOperation.SearchResultReference: this.Set(1, new RfcSearchResultReference(bais, content.Length)); break; case LdapOperation.BindResponse: this.Set(1, new RfcBindResponse(bais, content.Length)); break; case LdapOperation.ExtendedResponse: this.Set(1, new RfcExtendedResponse(bais, content.Length)); break; case LdapOperation.IntermediateResponse: this.Set(1, new RfcIntermediateResponse(bais, content.Length)); break; case LdapOperation.ModifyResponse: this.Set(1, new RfcModifyResponse(bais, content.Length)); break; default: throw new InvalidOperationException($"RfcLdapMessage: Invalid tag: {protocolOpId.Tag}"); } // decode optional implicitly tagged controls from Asn1Tagged type to // to RFC 2251 types. if(this.Size() <= 2) { return; } Asn1Tagged controls = (Asn1Tagged)this.Get(2); content = ((Asn1OctetString)controls.TaggedValue).ByteValue(); using(MemoryStream ms = new MemoryStream(content.ToByteArray())) { this.Set(2, new RfcControls(ms, content.Length)); } } public Int32 MessageId => ((Asn1Integer)this.Get(0)).IntValue(); /// <summary> Returns this RfcLdapMessage's message type.</summary> public LdapOperation Type => (LdapOperation)this.Get(1).GetIdentifier().Tag; public Asn1Object Response => this.Get(1); public String RequestDn => ((IRfcRequest)this._op).GetRequestDN(); public LdapMessage RequestingMessage { get; set; } public IRfcRequest GetRequest() => (IRfcRequest)this.Get(1); public Boolean IsRequest() => this.Get(1) is IRfcRequest; } /// <summary> /// Represents Ldap Controls. /// <pre> /// Controls ::= SEQUENCE OF Control /// </pre> /// </summary> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" /> internal class RfcControls : Asn1SequenceOf { public const Int32 Controls = 0; public RfcControls() : base(5) { } public RfcControls(Stream stream, Int32 len) : base(stream, len) { // Convert each SEQUENCE element to a Control for(Int32 i = 0; i < this.Size(); i++) { RfcControl tempControl = new RfcControl((Asn1Sequence)this.Get(i)); this.Set(i, tempControl); } } public void Add(RfcControl control) => base.Add(control); public void Set(Int32 index, RfcControl control) => base.Set(index, control); public override Asn1Identifier GetIdentifier() => new Asn1Identifier(Controls, true); } /// <summary> /// This interface represents RfcLdapMessages that contain a response from a /// server. /// If the protocol operation of the RfcLdapMessage is of this type, /// it contains at least an RfcLdapResult. /// </summary> internal interface IRfcResponse { /// <summary> /// Gets the result code. /// </summary> /// <returns>Asn1Enumerated.</returns> Asn1Enumerated GetResultCode(); /// <summary> /// Gets the matched dn. /// </summary> /// <returns>RfcLdapDN.</returns> Asn1OctetString GetMatchedDN(); /// <summary> /// Gets the error message. /// </summary> /// <returns>RfcLdapString.</returns> Asn1OctetString GetErrorMessage(); /// <summary> /// Gets the referral. /// </summary> /// <returns>Asn1SequenceOf.</returns> Asn1SequenceOf GetReferral(); } /// <summary> /// This interface represents Protocol Operations that are requests from a /// client. /// </summary> internal interface IRfcRequest { /// <summary> /// Builds a new request using the data from the this object. /// </summary> /// <returns>A <see cref="System.String" />.</returns> String GetRequestDN(); } /// <summary> /// Represents an LdapResult. /// <pre> /// LdapResult ::= SEQUENCE { /// resultCode ENUMERATED { /// success (0), /// operationsError (1), /// protocolError (2), /// timeLimitExceeded (3), /// sizeLimitExceeded (4), /// compareFalse (5), /// compareTrue (6), /// authMethodNotSupported (7), /// strongAuthRequired (8), /// -- 9 reserved -- /// referral (10), -- new /// adminLimitExceeded (11), -- new /// unavailableCriticalExtension (12), -- new /// confidentialityRequired (13), -- new /// saslBindInProgress (14), -- new /// noSuchAttribute (16), /// undefinedAttributeType (17), /// inappropriateMatching (18), /// constraintViolation (19), /// attributeOrValueExists (20), /// invalidAttributeSyntax (21), /// -- 22-31 unused -- /// noSuchObject (32), /// aliasProblem (33), /// invalidDNSyntax (34), /// -- 35 reserved for undefined isLeaf -- /// aliasDereferencingProblem (36), /// -- 37-47 unused -- /// inappropriateAuthentication (48), /// invalidCredentials (49), /// insufficientAccessRights (50), /// busy (51), /// unavailable (52), /// unwillingToPerform (53), /// loopDetect (54), /// -- 55-63 unused -- /// namingViolation (64), /// objectClassViolation (65), /// notAllowedOnNonLeaf (66), /// notAllowedOnRDN (67), /// entryAlreadyExists (68), /// objectClassModsProhibited (69), /// -- 70 reserved for CLdap -- /// affectsMultipleDSAs (71), -- new /// -- 72-79 unused -- /// other (80) }, /// -- 81-90 reserved for APIs -- /// matchedDN LdapDN, /// errorMessage LdapString, /// referral [3] Referral OPTIONAL } /// </pre> /// </summary> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> /// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" /> internal class RfcLdapResult : Asn1Sequence, IRfcResponse { public const Int32 Referral = 3; [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0068:Empfohlenes Dispose-Muster verwenden", Justification = "<Ausstehend>")] public RfcLdapResult(Stream stream, Int32 len) : base(stream, len) { // Decode optional referral from Asn1OctetString to Referral. if(this.Size() <= 3) { return; } Asn1Tagged obj = (Asn1Tagged)this.Get(3); Asn1Identifier id = obj.GetIdentifier(); if(id.Tag != Referral) { return; } SByte[] content = ((Asn1OctetString)obj.TaggedValue).ByteValue(); this.Set(3, new Asn1SequenceOf(new MemoryStream(content.ToByteArray()), content.Length)); } public Asn1Enumerated GetResultCode() => (Asn1Enumerated)this.Get(0); public Asn1OctetString GetMatchedDN() => new Asn1OctetString(((Asn1OctetString)this.Get(1)).ByteValue()); public Asn1OctetString GetErrorMessage() => new Asn1OctetString(((Asn1OctetString)this.Get(2)).ByteValue()); public Asn1SequenceOf GetReferral() => this.Size() > 3 ? (Asn1SequenceOf)this.Get(3) : null; } /// <summary> /// Represents an Ldap Search Result Done Response. /// <pre> /// SearchResultDone ::= [APPLICATION 5] LdapResult /// </pre> /// </summary> /// <seealso cref="Unosquare.Swan.Networking.Ldap.RfcLdapResult" /> internal class RfcSearchResultDone : RfcLdapResult { public RfcSearchResultDone(Stream stream, Int32 len) : base(stream, len) { } public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResult); } /// <summary> /// Represents an Ldap Search Result Entry. /// <pre> /// SearchResultEntry ::= [APPLICATION 4] SEQUENCE { /// objectName LdapDN, /// attributes PartialAttributeList } /// </pre> /// </summary> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" /> internal sealed class RfcSearchResultEntry : Asn1Sequence { public RfcSearchResultEntry(Stream stream, Int32 len) : base(stream, len) { } public String ObjectName => ((Asn1OctetString)this.Get(0)).StringValue(); public Asn1Sequence Attributes => (Asn1Sequence)this.Get(1); public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResponse); } /// <summary> /// Represents an Ldap Message ID. /// <pre> /// MessageID ::= INTEGER (0 .. maxInt) /// maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) -- /// Note: The creation of a MessageID should be hidden within the creation of /// an RfcLdapMessage. The MessageID needs to be in sequence, and has an /// upper and lower limit. There is never a case when a user should be /// able to specify the MessageID for an RfcLdapMessage. The MessageID() /// class should be package protected. (So the MessageID value isn't /// arbitrarily run up.) /// </pre></summary> /// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Integer" /> internal class RfcMessageID : Asn1Integer { private static Int32 _messageId; private static readonly Object SyncRoot = new Object(); /// <summary> /// Initializes a new instance of the <see cref="RfcMessageID"/> class. /// Creates a MessageID with an auto incremented Asn1Integer value. /// Bounds: (0 .. 2,147,483,647) (2^^31 - 1 or Integer.MAX_VALUE) /// MessageID zero is never used in this implementation. Always /// start the messages with one. /// </summary> protected internal RfcMessageID() : base(MessageId) { } private static Int32 MessageId { get { lock(SyncRoot) { return _messageId < Int32.MaxValue ? ++_messageId : (_messageId = 1); } } } } }