1use crate::LdapClient;
2use crate::*;
3use ldap3_proto::control::LdapControl;
4use serde::{Deserialize, Serialize};
5use serde_with::{base64, formats, serde_as};
6
7#[derive(Debug, Deserialize, Serialize, PartialEq)]
8pub enum LdapSyncStateValue {
9 Present,
10 Add,
11 Modify,
12 Delete,
13}
14
15impl From<SyncStateValue> for LdapSyncStateValue {
16 fn from(v: SyncStateValue) -> LdapSyncStateValue {
17 match v {
18 SyncStateValue::Present => LdapSyncStateValue::Present,
19 SyncStateValue::Add => LdapSyncStateValue::Add,
20 SyncStateValue::Modify => LdapSyncStateValue::Modify,
21 SyncStateValue::Delete => LdapSyncStateValue::Delete,
22 }
23 }
24}
25
26#[derive(Debug, Deserialize, Serialize)]
27pub struct LdapSyncReplEntry {
28 pub entry_uuid: Uuid,
29 pub state: LdapSyncStateValue,
30 pub entry: LdapEntry,
31}
32
33#[serde_as]
34#[derive(Debug, Deserialize, Serialize)]
35pub enum LdapSyncRepl {
36 Success {
37 #[serde_as(as = "Option<base64::Base64<base64::UrlSafe, formats::Unpadded>>")]
38 cookie: Option<Vec<u8>>,
39 refresh_deletes: bool,
40 entries: Vec<LdapSyncReplEntry>,
41 delete_uuids: Option<Vec<Uuid>>,
42 present_uuids: Option<Vec<Uuid>>,
43 },
44 RefreshRequired,
45}
46
47impl LdapClient {
48 #[tracing::instrument(level = "debug", skip_all)]
49 pub async fn syncrepl<S: Into<String>>(
50 &mut self,
51 basedn: S,
52 filter: LdapFilter,
53 cookie: Option<Vec<u8>>,
54 mode: SyncRequestMode,
55 ) -> crate::LdapResult<LdapSyncRepl> {
56 let msgid = self.get_next_msgid();
57
58 let msg = LdapMsg {
59 msgid,
60 op: LdapOp::SearchRequest(LdapSearchRequest {
61 base: basedn.into(),
62 scope: LdapSearchScope::Subtree,
63 aliases: LdapDerefAliases::Never,
64 sizelimit: 0,
65 timelimit: 0,
66 typesonly: false,
67 filter,
68 attrs: vec!["*".to_string(), "+".to_string()],
69 }),
70 ctrl: vec![LdapControl::SyncRequest {
71 criticality: true,
72 mode,
73 cookie,
74 reload_hint: false,
75 }],
76 };
77
78 self.write_transport.send(msg).await?;
79
80 let mut entries = Vec::new();
81 let mut delete_uuids: Option<Vec<_>> = None;
82 let mut present_uuids: Option<Vec<_>> = None;
83
84 loop {
85 let mut msg = self.read_transport.next().await?;
86
87 match msg.op {
88 LdapOp::SearchResultDone(proto::LdapResult {
90 code: LdapResultCode::Success,
91 message: _,
92 matcheddn: _,
93 referral: _,
94 }) => {
95 trace!("SearchResultDone");
96 if let Some(LdapControl::SyncDone {
97 cookie,
98 refresh_deletes,
99 }) = msg.ctrl.pop()
100 {
101 break Ok(LdapSyncRepl::Success {
102 cookie,
103 refresh_deletes,
104 entries,
105 delete_uuids,
106 present_uuids,
107 });
108 } else {
109 error!("Invalid Sync Control encountered");
110 break Err(LdapError::InvalidProtocolState);
111 }
112 }
113 LdapOp::SearchResultDone(proto::LdapResult {
115 code: LdapResultCode::EsyncRefreshRequired,
116 message,
117 matcheddn: _,
118 referral: _,
119 }) => {
120 error!(%message);
121 break Ok(LdapSyncRepl::RefreshRequired);
122 }
123 LdapOp::IntermediateResponse(LdapIntermediateResponse::SyncInfoIdSet {
124 cookie: _,
125 refresh_deletes,
126 syncuuids,
127 }) => {
128 trace!(?refresh_deletes, ?syncuuids);
129 if refresh_deletes {
138 let d_uuids = delete_uuids.get_or_insert_with(Vec::default);
139 d_uuids.extend(syncuuids.into_iter());
140 } else {
141 let p_uuids = present_uuids.get_or_insert_with(Vec::default);
142 p_uuids.extend(syncuuids.into_iter());
143 }
144 }
145 LdapOp::IntermediateResponse(LdapIntermediateResponse::SyncInfoRefreshDelete {
146 cookie: _,
147 done: false,
148 }) => {
149 let _d_uuids = delete_uuids.get_or_insert_with(Vec::default);
153 }
154 LdapOp::IntermediateResponse(
155 LdapIntermediateResponse::SyncInfoRefreshPresent {
156 cookie: _,
157 done: false,
158 },
159 ) => {
160 let _p_uuids = present_uuids.get_or_insert_with(Vec::default);
164 }
165 LdapOp::SearchResultEntry(entry) => {
166 if let Some(LdapControl::SyncState {
167 state,
168 entry_uuid,
169 cookie,
170 }) = msg.ctrl.pop()
171 {
172 if let Some(cookie) = cookie {
173 trace!(?cookie);
174 }
175 entries.push(LdapSyncReplEntry {
176 entry_uuid,
177 state: state.into(),
178 entry: entry.into(),
179 })
180 } else {
181 error!("Invalid Sync Control encountered");
182 break Err(LdapError::InvalidProtocolState);
183 }
184 }
185 LdapOp::SearchResultReference(_search_reference) => {
186 }
188 LdapOp::SearchResultDone(proto::LdapResult {
190 code,
191 message,
192 matcheddn: _,
193 referral: _,
194 }) => {
195 error!(%message);
196 break Err(LdapError::from(code));
197 }
198 op => {
199 trace!(?op, "<<<<==== ");
200 break Err(LdapError::InvalidProtocolState);
201 }
202 };
203 }
204 }
205}