Skip to main content

ldap3_client/
syncrepl.rs

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                // Happy cases here
89                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                // Indicate to the client they need to refresh
114                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                    //  Multiple empty entries with a Sync State Control of state delete
130                    // SHOULD be coalesced into one or more Sync Info Messages of syncIdSet
131                    // value with refreshDeletes set to TRUE.  syncUUIDs contain a set of
132                    // UUIDs of the entries and references that have been deleted from the
133                    // content since the last Sync Operation.  syncUUIDs may be empty.  The
134                    // Sync Info Message of syncIdSet may contain a cookie to represent the
135                    // state of the content after performing the synchronization of the
136                    // entries in the set.
137                    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                    // These are no-ops that are skipped for our purposes
150                    // They are intended to deliniate the separate phases, but we actually don't
151                    // care until we get the search result done.
152                    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                    // These are no-ops that are skipped for our purposes
161                    // They are intended to deliniate the separate phases, but we actually don't
162                    // care until we get the search result done.
163                    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                    // pass
187                }
188                // Error cases below
189                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}