Skip to content
2 changes: 1 addition & 1 deletion proto/examples/sasltest/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
#[allow(clippy::expect_used, clippy::expect_fun_call)]
let addr = SocketAddr::from_str(&ldap_server_addr).expect(&format!(
"Unable to parse address, addr is {:?}",
&ldap_server_addr
ldap_server_addr
));

let tcpstream = TcpStream::connect(addr).await?;
Expand Down
2 changes: 1 addition & 1 deletion proto/examples/search/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let ldap_server_addr = env::var("LDAP_SERVER_ADDR").expect("LDAP_SERVER_ADDR not set"); // domain.com:port
let ldap_username_dn = env::var("LDAP_USERNAME_DN").expect("LDAP_USERNAME_DN not set"); // username@domain
let addr = SocketAddr::from_str(&ldap_server_addr)
.unwrap_or_else(|_| panic!("Unable to parse address, addr is {:?}", &ldap_server_addr));
.unwrap_or_else(|_| panic!("Unable to parse address, addr is {:?}", ldap_server_addr));

let tcpstream = TcpStream::connect(addr).await?;

Expand Down
86 changes: 81 additions & 5 deletions proto/src/control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,78 @@ impl TryFrom<StructureTag> for LdapControl {

Ok(LdapControl::ManageDsaIT { criticality })
}
// RFC 2891 §1.1.1: Server-Side Sort request control.
// Wire: SEQUENCE OF SEQUENCE { attributeType OCTET STRING,
// [0] orderingRule OCTET STRING OPTIONAL,
// [1] reverseOrder BOOLEAN }
"1.2.840.113556.1.4.473" => {
let value = value_tag
.and_then(|t| t.match_class(TagClass::Universal))
.and_then(|t| t.match_id(Types::OctetString as u64))
.and_then(|t| t.expect_primitive())
.ok_or(LdapProtoError::ControlBer)?;

let mut parser = Parser::default();
let (_, tag) = parser
.parse(&value)
.map_err(|_| LdapProtoError::ControlBer)?;

let mut sort_keys = tag
.match_class(TagClass::Universal)
.and_then(|t| t.match_id(Types::Sequence as u64))
.and_then(|t| t.expect_constructed())
.ok_or(LdapProtoError::ControlBer)?;

let mut requests = Vec::with_capacity(sort_keys.len());
while let Some(key_tag) = sort_keys.pop() {
let mut inner = key_tag
.match_class(TagClass::Universal)
.and_then(|t| t.match_id(Types::Sequence as u64))
.and_then(|t| t.expect_constructed())
.ok_or(LdapProtoError::ControlBer)?;

// Inner tags are pushed in order attr, [0] rule, [1] bool;
// pop() yields them reverse: bool?, rule?, attr.
let mut reverse_order = false;
let mut ordering_rule = None;
while let Some(t) = inner.pop() {
// [1] reverseOrder — Context class, id 1, Boolean.
if t.class == TagClass::Context && t.id == 1 {
reverse_order = t
.expect_primitive()
.and_then(|bytes| bytes.first().copied())
.map(|b| b != 0)
.unwrap_or(false);
// [0] orderingRule — Context class, id 0, OctetString.
} else if t.class == TagClass::Context && t.id == 0 {
ordering_rule =
t.expect_primitive().map(|bytes| bytes_to_string!(bytes));
// attributeType is the one mandatory field.
} else if t.class == TagClass::Universal
&& t.id == Types::OctetString as u64
{
let bytes = t.expect_primitive().ok_or(LdapProtoError::ControlBer)?;
requests.push(ServerSortRequet {
attribute_name: bytes_to_string!(bytes),
ordering_rule: ordering_rule.take(),
reverse_order,
});
reverse_order = false;
} else {
return Err(LdapProtoError::ControlBer);
}
}
}

// RFC 2891 §1.1 requires at least one sort key.
if requests.is_empty() {
return Err(LdapProtoError::ControlBer);
}
requests.reverse();
Ok(LdapControl::ServerSort {
sort_requests: requests,
})
}
"1.2.840.113556.1.4.474" => {
let value = value_tag
.and_then(|t| t.match_class(TagClass::Universal))
Expand Down Expand Up @@ -906,11 +978,15 @@ impl From<LdapControl> for Tag {
id: 0,
}));
}
inner.push(Tag::Boolean(Boolean {
inner: sort_request.reverse_order,
class: TagClass::Context,
id: 1,
}));
// DEFAULT FALSE is absent on the wire (RFC 4511 §5.1).
if sort_request.reverse_order {
let reverse = Tag::Boolean(Boolean {
inner: true,
class: TagClass::Context,
id: 1,
});
inner.push(reverse);
}
Tag::Sequence(Sequence {
inner,
..Default::default()
Expand Down
105 changes: 104 additions & 1 deletion proto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ impl Encoder<LdapMsg> for LdapCodec {
#[cfg(test)]
mod tests {
use crate::LdapCodec;
use crate::control::LdapControl;
use crate::control::{LdapControl, ServerSortRequet};
use crate::error::LdapProtoError;
use crate::parse_ldap_filter_str;
use crate::proto::*;
Expand Down Expand Up @@ -184,6 +184,109 @@ mod tests {
});
}

#[test]
fn test_ldapserver_codec_bindresponse_saslcreds() {
let msg = LdapMsg {
msgid: 999999,
op: LdapOp::BindResponse(LdapBindResponse {
res: LdapResult {
code: LdapResultCode::Success,
matcheddn: "cn=Directory Manager".to_string(),
message: "It works!".to_string(),
referral: vec![],
},
saslcreds: Some(vec![0x01, 0x02, 0x03]),
}),
ctrl: vec![],
};

let mut buf = BytesMut::new();
let mut server_codec = LdapCodec::default();
assert!(server_codec.encode(msg.clone(), &mut buf).is_ok());
// serverSaslCreds is [7] IMPLICIT OCTET STRING (0x87) per RFC 4511 §4.2.2.
assert!(
buf.windows(5).any(|w| w == [0x87, 0x03, 0x01, 0x02, 0x03]),
"saslcreds missing from encoded bind response: {buf:02x?}"
);
let res = server_codec.decode(&mut buf).expect("failed to decode");
assert_eq!(res.expect("None found?"), msg);
}

#[test]
fn test_ldapserver_codec_searchresultreference() {
do_test!(LdapMsg {
msgid: 1,
op: LdapOp::SearchResultReference(LdapSearchResultReference {
uris: vec!["ldap://ldap.example.com/dc=example,dc=com".to_string()],
}),
ctrl: vec![],
});
}

#[test]
fn test_ldapserver_search_with_server_sort_control() {
do_test!(LdapMsg {
msgid: 1,
op: LdapOp::SearchRequest(LdapSearchRequest {
base: "dc=example,dc=com".to_string(),
scope: LdapSearchScope::Subtree,
aliases: LdapDerefAliases::Never,
sizelimit: 0,
timelimit: 0,
typesonly: false,
filter: LdapFilter::Present("objectClass".to_string()),
attrs: vec![],
}),
ctrl: vec![LdapControl::ServerSort {
sort_requests: vec![
ServerSortRequet {
attribute_name: "cn".to_string(),
ordering_rule: None,
reverse_order: false,
},
ServerSortRequet {
attribute_name: "sn".to_string(),
ordering_rule: Some("caseIgnoreOrderingMatch".to_string()),
reverse_order: true,
},
],
}],
});
}

#[test]
fn test_ldapserver_sort_control_default_reverse_omitted() {
let msg = LdapMsg {
msgid: 1,
op: LdapOp::SearchRequest(LdapSearchRequest {
base: "dc=example,dc=com".to_string(),
scope: LdapSearchScope::Subtree,
aliases: LdapDerefAliases::Never,
sizelimit: 0,
timelimit: 0,
typesonly: false,
filter: LdapFilter::Present("objectClass".to_string()),
attrs: vec![],
}),
ctrl: vec![LdapControl::ServerSort {
sort_requests: vec![ServerSortRequet {
attribute_name: "cn".to_string(),
ordering_rule: None,
reverse_order: false,
}],
}],
};

let mut buf = BytesMut::new();
let mut server_codec = LdapCodec::default();
assert!(server_codec.encode(msg, &mut buf).is_ok());
// reverseOrder DEFAULT FALSE is absent (RFC 4511 §5.1); [1] is 0x81.
assert!(
!buf.windows(2).any(|w| w == [0x81, 0x01]),
"default reverseOrder emitted: {buf:02x?}"
);
}

#[test]
fn test_ldapserver_codec_searchrequest() {
do_test!(LdapMsg {
Expand Down
Loading
Loading