RaspberryIO/Unosquare.Swan/Networking/Ldap/RfcLdapMessage.cs
2019-12-03 18:44:25 +01:00

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);
}
}
}
}
}