///|
/// Run a raising function that raises `LdapError`, converting the outcome to
/// a `Result`.
pub fn[T] result_of_ldap(f : () -> T raise LdapError) -> Result[T, LdapError] {
Ok(f()) catch {
e => Err(e)
}
}
///|
fn decode_error(msg : String) -> LdapError {
Decode(msg)
}
///|
fn expect_universal(
value : BerValue,
number : Int,
constructed : Bool,
) -> Unit raise LdapError {
if value.tag.class != Universal ||
value.tag.number != number ||
value.tag.constructed != constructed {
raise decode_error("expected universal tag \{number}")
}
}
///|
fn as_integer(value : BerValue) -> Int raise LdapError {
expect_universal(value, tag_integer, false)
match parse_integer(value.content, 8) {
Ok(v) => v
Err(e) => raise LdapError::Ber(e)
}
}
///|
fn as_boolean(value : BerValue) -> Bool raise LdapError {
expect_universal(value, tag_boolean, false)
match parse_boolean(value.content) {
Ok(v) => v
Err(e) => raise LdapError::Ber(e)
}
}
///|
fn as_enumerated(value : BerValue) -> Int raise LdapError {
expect_universal(value, tag_enumerated, false)
match parse_integer(value.content, 8) {
Ok(v) => v
Err(e) => raise LdapError::Ber(e)
}
}
///|
fn as_octets(value : BerValue) -> Bytes raise LdapError {
expect_universal(value, tag_octet_string, false)
value.content
}
///|
fn as_ldap_string(value : BerValue) -> String raise LdapError {
@utf8.decode_lossy(as_octets(value)[:])
}
///|
fn encode_ldap_string(s : String) -> Bytes {
encode_octet_string(@utf8.encode(s))
}
///|
/// Encode an LDAPResult's SEQUENCE content (RFC 4511 4.1.9).
fn encode_result_content(result : LdapResult) -> Bytes {
let mut content = encode_enumerated(result.result_code.to_int())
content = content + encode_ldap_string(result.matched_dn)
content = content + encode_ldap_string(result.diagnostic_message)
match result.referral {
Some(uris) =>
if !uris.is_empty() {
let mut ref_content = Bytes::new(0)
for uri in uris {
ref_content = ref_content + encode_ldap_string(uri)
}
content = content + encode_tlv(context_tag(3, true), ref_content)
}
None => ()
}
content
}
///|
fn decode_result_content(
children : Array[BerValue],
) -> LdapResult raise LdapError {
if children.length() < 3 {
raise decode_error("LDAPResult has fewer than 3 elements")
}
let code = ResultCode::from_int(as_enumerated(children[0]))
let matched_dn = as_ldap_string(children[1])
let diagnostic = as_ldap_string(children[2])
let mut referral : Array[String]? = None
if children.length() > 3 {
let extra = children[3]
if extra.tag.class == ContextSpecific &&
extra.tag.number == 3 &&
extra.tag.constructed {
let uris : Array[String] = []
for child in extra.children_or_empty() {
uris.push(as_ldap_string(child))
}
referral = Some(uris)
}
}
{ result_code: code, matched_dn, diagnostic_message: diagnostic, referral }
}
///|
/// Encode a BindRequest protocol op content.
fn encode_bind_request_content(req : BindRequest) -> Bytes {
let mut content = encode_integer(req.version)
content = content + encode_ldap_string(req.name)
match req.authentication {
Simple(password) =>
content = content +
encode_tlv(context_tag(0, false), @utf8.encode(password))
Sasl(creds) => {
let mut sasl = encode_ldap_string(creds.mechanism)
sasl = sasl + encode_octet_string(creds.credentials)
content = content + encode_tlv(context_tag(3, true), sasl)
}
}
content
}
///|
fn decode_bind_request(
children : Array[BerValue],
) -> BindRequest raise LdapError {
if children.length() < 3 {
raise decode_error("bindRequest has fewer than 3 elements")
}
let version = as_integer(children[0])
let name = as_ldap_string(children[1])
let auth = children[2]
let authentication = if auth.tag.class == ContextSpecific &&
auth.tag.number == 0 &&
!auth.tag.constructed {
Simple(@utf8.decode_lossy(auth.content[:]))
} else if auth.tag.class == ContextSpecific &&
auth.tag.number == 3 &&
auth.tag.constructed {
let kids = auth.children_or_empty()
if kids.is_empty() {
raise decode_error("saslCredentials has no mechanism")
}
let mechanism = as_ldap_string(kids[0])
let credentials = if kids.length() > 1 {
as_octets(kids[1])
} else {
Bytes::new(0)
}
Sasl(SaslCredentials::new(mechanism, credentials))
} else {
raise decode_error("invalid authentication choice")
}
{ version, name, authentication }
}
///|
fn encode_bind_response_content(resp : BindResponse) -> Bytes {
let mut content = encode_result_content(resp.result)
match resp.server_sasl_creds {
Some(creds) => content = content + encode_tlv(context_tag(7, false), creds)
None => ()
}
content
}
///|
fn decode_bind_response(
children : Array[BerValue],
) -> BindResponse raise LdapError {
let result = decode_result_content(children)
let mut sasl : Bytes? = None
for child in children {
if child.tag.class == ContextSpecific &&
child.tag.number == 7 &&
!child.tag.constructed {
sasl = Some(child.content)
}
}
{ result, server_sasl_creds: sasl }
}
///|
/// Encode a full LDAPMessage to its BER wire form.
pub fn encode_message(msg : LdapMessage) -> Result[Bytes, LdapError] {
result_of_ldap(fn() raise LdapError {
if msg.message_id < 0 {
raise decode_error("messageID must not be negative")
}
let mut content = encode_integer(msg.message_id)
match msg.op {
BindRequest(req) =>
content = content +
encode_tlv(application_tag(0, true), encode_bind_request_content(req))
BindResponse(resp) =>
content = content +
encode_tlv(
application_tag(1, true),
encode_bind_response_content(resp),
)
UnbindRequest =>
content = content + encode_tlv(application_tag(2, false), Bytes::new(0))
SearchRequest(req) =>
content = content +
encode_tlv(
application_tag(3, true),
encode_search_request_content(req),
)
SearchResultEntry(entry) =>
content = content +
encode_tlv(
application_tag(4, true),
encode_search_entry_content(entry),
)
SearchResultDone(result) =>
content = content +
encode_tlv(application_tag(5, true), encode_result_content(result))
SearchResultReference(refs) =>
content = content +
encode_tlv(application_tag(19, true), encode_ref_content(refs))
ExtendedRequest(req) =>
content = content +
encode_tlv(
application_tag(23, true),
encode_extended_request_content(req),
)
ExtendedResponse(resp) =>
content = content +
encode_tlv(
application_tag(24, true),
encode_extended_response_content(resp),
)
AbandonRequest(id) =>
content = content +
encode_tlv(application_tag(16, false), integer_content(id))
ModifyRequest(req) =>
content = content +
encode_tlv(
application_tag(6, true),
encode_modify_request_content(req),
)
ModifyResponse(resp) =>
content = content +
encode_tlv(
application_tag(7, true),
encode_result_content(resp.result),
)
AddRequest(req) =>
content = content +
encode_tlv(application_tag(8, true), encode_add_request_content(req))
AddResponse(resp) =>
content = content +
encode_tlv(
application_tag(9, true),
encode_result_content(resp.result),
)
DelRequest(req) =>
content = content +
encode_tlv(application_tag(10, false), @utf8.encode(req.entry))
DelResponse(resp) =>
content = content +
encode_tlv(
application_tag(11, true),
encode_result_content(resp.result),
)
ModifyDnRequest(req) =>
content = content +
encode_tlv(
application_tag(12, true),
encode_modify_dn_request_content(req),
)
ModifyDnResponse(resp) =>
content = content +
encode_tlv(
application_tag(13, true),
encode_result_content(resp.result),
)
CompareRequest(req) =>
content = content +
encode_tlv(
application_tag(14, true),
encode_compare_request_content(req),
)
CompareResponse(resp) =>
content = content +
encode_tlv(
application_tag(15, true),
encode_result_content(resp.result),
)
}
match msg.controls {
Some(controls) =>
if !controls.is_empty() {
let mut ctrl_seq = Bytes::new(0)
for ctrl in controls {
let mut c = encode_ldap_string(ctrl.control_type)
if ctrl.criticality {
c = c + encode_boolean(true)
}
if ctrl.control_value.length() > 0 {
c = c + encode_octet_string(ctrl.control_value)
}
ctrl_seq = ctrl_seq + encode_sequence([c])
}
content = content + encode_tlv(context_tag(0, true), ctrl_seq)
}
None => ()
}
encode_sequence([content])
})
}
///|
/// Decode a full LDAPMessage from its BER wire form.
pub fn decode_message(
bytes : Bytes,
limits : BerLimits?,
) -> Result[LdapMessage, LdapError] {
result_of_ldap(fn() raise LdapError {
let value = match decode_ber(bytes, limits) {
Ok(v) => v
Err(e) => raise LdapError::Ber(e)
}
expect_universal(value, tag_sequence, true)
let children = value.children_or_empty()
if children.length() < 2 {
raise decode_error("LDAPMessage has fewer than 2 elements")
}
let message_id = as_integer(children[0])
if message_id < 0 {
raise LdapError::InvalidMessageId(message_id)
}
let op_value = children[1]
let op = decode_protocol_op(op_value)
let mut controls : Array[Control]? = None
if children.length() > 2 {
let ctrl_value = children[2]
if ctrl_value.tag.class == ContextSpecific &&
ctrl_value.tag.number == 0 &&
ctrl_value.tag.constructed {
let seq = ctrl_value.children_or_empty()
let ctrls : Array[Control] = []
for ctrl in seq {
let parts = ctrl.children_or_empty()
if parts.length() < 1 {
raise decode_error("control has no type")
}
let ctype = as_ldap_string(parts[0])
let mut critical = false
let mut cvalue = Bytes::new(0)
// `criticality` is optional (DEFAULT FALSE) so position varies:
// either `type, value` or `type, criticality, value`.
for i in 1.. ProtocolOp raise LdapError {
if value.tag.class != Application {
raise decode_error("protocolOp must be application class")
}
let kids = value.children_or_empty()
match value.tag.number {
0 => BindRequest(decode_bind_request(kids))
1 => BindResponse(decode_bind_response(kids))
2 => UnbindRequest
3 => SearchRequest(decode_search_request(kids))
4 => SearchResultEntry(decode_search_entry(kids))
5 => SearchResultDone(decode_result_content(kids))
19 => SearchResultReference(decode_ref(kids))
6 => ModifyRequest(decode_modify_request(kids))
7 => ModifyResponse(ModifyResponse::new(decode_result_content(kids)))
8 => AddRequest(decode_add_request(kids))
9 => AddResponse(AddResponse::new(decode_result_content(kids)))
10 => DelRequest(DelRequest::new(@utf8.decode_lossy(value.content[:])))
11 => DelResponse(DelResponse::new(decode_result_content(kids)))
12 => ModifyDnRequest(decode_modify_dn_request(kids))
13 => ModifyDnResponse(ModifyDnResponse::new(decode_result_content(kids)))
14 => CompareRequest(decode_compare_request(kids))
15 => CompareResponse(CompareResponse::new(decode_result_content(kids)))
23 => ExtendedRequest(decode_extended_request(kids))
24 => ExtendedResponse(decode_extended_response(kids))
16 =>
// Implicitly-tagged [APPLICATION 17] MessageID: primitive INTEGER content.
match parse_integer(value.content, 8) {
Ok(id) => AbandonRequest(id)
Err(e) => raise LdapError::Ber(e)
}
n => raise LdapError::UnexpectedOp(n)
}
}
///|
fn encode_search_request_content(req : SearchRequest) -> Bytes {
let mut content = encode_ldap_string(req.base_object)
content = content + encode_enumerated(req.scope.to_int())
content = content + encode_enumerated(req.deref_aliases.to_int())
content = content + encode_integer(req.size_limit)
content = content + encode_integer(req.time_limit)
content = content + encode_boolean(req.types_only)
content = content + encode_filter(req.filter)
let mut attrs = Bytes::new(0)
for attr in req.attributes {
attrs = attrs + encode_ldap_string(attr)
}
content = content + encode_sequence([attrs])
content
}
///|
fn decode_search_request(
children : Array[BerValue],
) -> SearchRequest raise LdapError {
if children.length() < 8 {
raise decode_error("searchRequest has fewer than 8 elements")
}
let base_object = as_ldap_string(children[0])
let scope = match SearchScope::from_int(as_enumerated(children[1])) {
Some(s) => s
None => raise decode_error("invalid search scope")
}
let deref = match DerefAliases::from_int(as_enumerated(children[2])) {
Some(d) => d
None => raise decode_error("invalid derefAliases")
}
let size_limit = as_integer(children[3])
let time_limit = as_integer(children[4])
let types_only = as_boolean(children[5])
let filter = decode_filter_value(children[6])
let attributes : Array[String] = []
let attr_seq = children[7]
for attr in attr_seq.children_or_empty() {
attributes.push(as_ldap_string(attr))
}
{
base_object,
scope,
deref_aliases: deref,
size_limit,
time_limit,
types_only,
attributes,
filter,
}
}
///|
fn encode_search_entry_content(entry : SearchResultEntry) -> Bytes {
let mut attrs = Bytes::new(0)
for pa in entry.attributes {
let mut pa_content = encode_ldap_string(pa.attr_type)
let mut values = Bytes::new(0)
for v in pa.values {
values = values + encode_octet_string(v)
}
pa_content = pa_content + encode_set([values])
attrs = attrs + encode_sequence([pa_content])
}
encode_ldap_string(entry.object_name) + encode_sequence([attrs])
}
///|
fn decode_search_entry(
children : Array[BerValue],
) -> SearchResultEntry raise LdapError {
if children.length() != 2 {
raise decode_error("searchResultEntry must have two elements")
}
let object_name = as_ldap_string(children[0])
let attributes : Array[PartialAttribute] = []
let attr_list = children[1]
for pa_value in attr_list.children_or_empty() {
let pa_kids = pa_value.children_or_empty()
if pa_kids.length() != 2 {
raise decode_error("partialAttribute must have two elements")
}
let attr_type = as_ldap_string(pa_kids[0])
let values : Array[Bytes] = []
for v in pa_kids[1].children_or_empty() {
values.push(as_octets(v))
}
attributes.push(PartialAttribute::new(attr_type, values))
}
{ object_name, attributes }
}
///|
fn encode_ref_content(refs : SearchResultReference) -> Bytes {
let mut content = Bytes::new(0)
for uri in refs.uris {
content = content + encode_ldap_string(uri)
}
content
}
///|
fn decode_ref(
children : Array[BerValue],
) -> SearchResultReference raise LdapError {
let out : Array[String] = []
for uri in children {
out.push(as_ldap_string(uri))
}
SearchResultReference::new(out)
}
///|
fn encode_partial_attribute_content(pa : PartialAttribute) -> Bytes {
let mut values = Bytes::new(0)
for v in pa.values {
values = values + encode_octet_string(v)
}
encode_ldap_string(pa.attr_type) + encode_set([values])
}
///|
fn encode_modify_request_content(req : ModifyRequest) -> Bytes {
let mut changes = Bytes::new(0)
for change in req.changes {
let mut c = encode_enumerated(change.operation.to_int())
c = c +
encode_sequence([encode_partial_attribute_content(change.modification)])
changes = changes + encode_sequence([c])
}
encode_ldap_string(req.object) + encode_sequence([changes])
}
///|
fn decode_modify_request(
children : Array[BerValue],
) -> ModifyRequest raise LdapError {
if children.length() != 2 {
raise decode_error("modifyRequest must have two elements")
}
let object = as_ldap_string(children[0])
let changes : Array[ModifyRequestChange] = []
for change_value in children[1].children_or_empty() {
let parts = change_value.children_or_empty()
if parts.length() != 2 {
raise decode_error("change must have two elements")
}
let operation = match ModifyOperation::from_int(as_enumerated(parts[0])) {
Some(op) => op
None => raise decode_error("invalid modify operation")
}
let pa_parts = parts[1].children_or_empty()
if pa_parts.length() != 2 {
raise decode_error("partialAttribute must have two elements")
}
let attr_type = as_ldap_string(pa_parts[0])
let values : Array[Bytes] = []
for v in pa_parts[1].children_or_empty() {
values.push(as_octets(v))
}
changes.push(
ModifyRequestChange::new(
operation,
PartialAttribute::new(attr_type, values),
),
)
}
ModifyRequest::new(object, changes)
}
///|
fn encode_add_request_content(req : AddRequest) -> Bytes {
let mut attrs = Bytes::new(0)
for attr in req.attributes {
attrs = attrs +
encode_sequence([
encode_ldap_string(attr.attr_type) + encode_set_partial(attr.values),
])
}
encode_ldap_string(req.entry) + encode_sequence([attrs])
}
///|
fn encode_set_partial(values : Array[Bytes]) -> Bytes {
let mut out = Bytes::new(0)
for v in values {
out = out + encode_octet_string(v)
}
encode_set([out])
}
///|
fn decode_add_request(children : Array[BerValue]) -> AddRequest raise LdapError {
if children.length() != 2 {
raise decode_error("addRequest must have two elements")
}
let entry = as_ldap_string(children[0])
let attributes : Array[Attribute] = []
for attr_value in children[1].children_or_empty() {
let parts = attr_value.children_or_empty()
if parts.length() != 2 {
raise decode_error("attribute must have two elements")
}
let attr_type = as_ldap_string(parts[0])
let values : Array[Bytes] = []
for v in parts[1].children_or_empty() {
values.push(as_octets(v))
}
attributes.push(Attribute::new(attr_type, values))
}
AddRequest::new(entry, attributes)
}
///|
fn encode_modify_dn_request_content(req : ModifyDnRequest) -> Bytes {
let mut content = encode_ldap_string(req.entry)
content = content + encode_ldap_string(req.newrdn)
content = content + encode_boolean(req.delete_old_rdn)
match req.new_superior {
Some(superior) =>
content = content +
encode_tlv(context_tag(0, false), @utf8.encode(superior))
None => ()
}
content
}
///|
fn decode_modify_dn_request(
children : Array[BerValue],
) -> ModifyDnRequest raise LdapError {
if children.length() < 3 {
raise decode_error("modifyDNRequest has fewer than 3 elements")
}
let entry = as_ldap_string(children[0])
let newrdn = as_ldap_string(children[1])
let delete_old_rdn = as_boolean(children[2])
let mut new_superior : String? = None
if children.length() > 3 {
let extra = children[3]
if extra.tag.class == ContextSpecific && extra.tag.number == 0 {
new_superior = Some(@utf8.decode_lossy(extra.content[:]))
}
}
ModifyDnRequest::new(entry, newrdn, delete_old_rdn, new_superior)
}
///|
fn encode_compare_request_content(req : CompareRequest) -> Bytes {
let ava = encode_sequence([
encode_ldap_string(req.ava.attribute_desc) +
encode_octet_string(req.ava.assertion_value),
])
encode_ldap_string(req.entry) + ava
}
///|
fn decode_compare_request(
children : Array[BerValue],
) -> CompareRequest raise LdapError {
if children.length() != 2 {
raise decode_error("compareRequest must have two elements")
}
let entry = as_ldap_string(children[0])
let ava_parts = children[1].children_or_empty()
if ava_parts.length() != 2 {
raise decode_error("attributeValueAssertion must have two elements")
}
CompareRequest::new(
entry,
AttributeValueAssertion::new(
as_ldap_string(ava_parts[0]),
as_octets(ava_parts[1]),
),
)
}
///|
fn encode_extended_request_content(req : ExtendedRequest) -> Bytes {
let mut content = encode_tlv(
context_tag(0, false),
@utf8.encode(req.request_name),
)
match req.request_value {
Some(value) => content = content + encode_tlv(context_tag(1, false), value)
None => ()
}
content
}
///|
fn decode_extended_request(
children : Array[BerValue],
) -> ExtendedRequest raise LdapError {
if children.is_empty() {
raise decode_error("extendedRequest has no requestName")
}
let name_value = children[0]
if !(name_value.tag.class == ContextSpecific &&
name_value.tag.number == 0 &&
!name_value.tag.constructed) {
raise decode_error("extendedRequest requestName must be [0]")
}
let mut request_value : Bytes? = None
if children.length() > 1 {
let v = children[1]
if v.tag.class == ContextSpecific && v.tag.number == 1 && !v.tag.constructed {
request_value = Some(v.content)
}
}
ExtendedRequest::new(@utf8.decode_lossy(name_value.content[:]), request_value)
}
///|
fn encode_extended_response_content(resp : ExtendedResponse) -> Bytes {
let mut content = encode_result_content(resp.result)
match resp.response_name {
Some(name) =>
content = content + encode_tlv(context_tag(10, false), @utf8.encode(name))
None => ()
}
match resp.response_value {
Some(value) => content = content + encode_tlv(context_tag(11, false), value)
None => ()
}
content
}
///|
fn decode_extended_response(
children : Array[BerValue],
) -> ExtendedResponse raise LdapError {
let result = decode_result_content(children)
let resp = ExtendedResponse::new(result)
for child in children {
if child.tag.class == ContextSpecific && !child.tag.constructed {
if child.tag.number == 10 {
resp.response_name = Some(@utf8.decode_lossy(child.content[:]))
} else if child.tag.number == 11 {
resp.response_value = Some(child.content)
}
}
}
resp
}