379 lines
13 KiB
C#
379 lines
13 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |