1use std::iter::once;
5use std::sync::Arc;
6
7use crate::event::{CreateEvent, ModifyEvent};
8use crate::plugins::Plugin;
9use crate::prelude::*;
10use crate::utils::uuid_to_gid_u32;
11
12const GID_SYSTEM_NUMBER_PREFIX: u32 = 0x7000_0000;
27const GID_SYSTEM_NUMBER_MASK: u32 = 0x0fff_ffff;
28
29pub const GID_REGULAR_USER_MIN: u32 = 1000;
33pub const GID_REGULAR_USER_MAX: u32 = 60000;
34
35pub const GID_UNUSED_A_MIN: u32 = 60578;
38pub const GID_UNUSED_A_MAX: u32 = 61183;
39
40pub const GID_UNUSED_B_MIN: u32 = 65520;
43pub const GID_UNUSED_B_MAX: u32 = 65533;
44
45pub const GID_UNUSED_C_MIN: u32 = 65536;
49const GID_UNUSED_C_MAX: u32 = 524287;
50
51const GID_NSPAWN_MIN: u32 = 524288;
54const GID_NSPAWN_MAX: u32 = 1879048191;
55
56const GID_UNUSED_D_MIN: u32 = 0x7000_0000;
57pub const GID_UNUSED_D_MAX: u32 = 0x7fff_ffff;
58
59pub struct GidNumber {}
64
65fn apply_gidnumber<T: Clone>(e: &mut Entry<EntryInvalid, T>) -> Result<(), OperationError> {
66    if (e.attribute_equality(Attribute::Class, &EntryClass::PosixGroup.into())
67        || e.attribute_equality(Attribute::Class, &EntryClass::PosixAccount.into()))
68        && !e.attribute_pres(Attribute::GidNumber)
69    {
70        let u_ref = e
71            .get_uuid()
72            .ok_or(OperationError::InvalidEntryState)
73            .inspect_err(|_e| {
74                admin_error!("Invalid Entry State - Missing UUID");
75            })?;
76
77        let gid = uuid_to_gid_u32(u_ref);
78
79        let gid = gid & GID_SYSTEM_NUMBER_MASK;
82        let gid = gid | GID_SYSTEM_NUMBER_PREFIX;
83
84        let gid_v = Value::new_uint32(gid);
85        admin_info!("Generated {} for {:?}", gid, u_ref);
86        e.set_ava(&Attribute::GidNumber, once(gid_v));
87        Ok(())
88    } else if let Some(gid) = e.get_ava_single_uint32(Attribute::GidNumber) {
89        if (GID_REGULAR_USER_MIN..=GID_REGULAR_USER_MAX).contains(&gid)
91            || (GID_UNUSED_A_MIN..=GID_UNUSED_A_MAX).contains(&gid)
92            || (GID_UNUSED_B_MIN..= GID_UNUSED_B_MAX).contains(&gid)
93            || (GID_UNUSED_C_MIN..=GID_UNUSED_C_MAX).contains(&gid)
94            || (GID_NSPAWN_MIN..=GID_NSPAWN_MAX).contains(&gid)
102            || (GID_UNUSED_D_MIN..=GID_UNUSED_D_MAX).contains(&gid)
103        {
104            Ok(())
105        } else {
106            error!(
109                "Requested GID ({}) overlaps a system range. Allowed ranges are {} to {}, {} to {} and {} to {}",
110                gid,
111                GID_REGULAR_USER_MIN, GID_REGULAR_USER_MAX,
112                GID_UNUSED_C_MIN, GID_UNUSED_C_MAX,
113                GID_UNUSED_D_MIN, GID_UNUSED_D_MAX
114            );
115            Err(OperationError::PL0001GidOverlapsSystemRange)
116        }
117    } else {
118        Ok(())
119    }
120}
121
122impl Plugin for GidNumber {
123    fn id() -> &'static str {
124        "plugin_gidnumber"
125    }
126
127    #[instrument(level = "debug", name = "gidnumber_pre_create_transform", skip_all)]
128    fn pre_create_transform(
129        _qs: &mut QueryServerWriteTransaction,
130        cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
131        _ce: &CreateEvent,
132    ) -> Result<(), OperationError> {
133        cand.iter_mut().try_for_each(apply_gidnumber)
134    }
135
136    #[instrument(level = "debug", name = "gidnumber_pre_modify", skip_all)]
137    fn pre_modify(
138        _qs: &mut QueryServerWriteTransaction,
139        _pre_cand: &[Arc<EntrySealedCommitted>],
140        cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
141        _me: &ModifyEvent,
142    ) -> Result<(), OperationError> {
143        cand.iter_mut().try_for_each(apply_gidnumber)
144    }
145
146    #[instrument(level = "debug", name = "gidnumber_pre_batch_modify", skip_all)]
147    fn pre_batch_modify(
148        _qs: &mut QueryServerWriteTransaction,
149        _pre_cand: &[Arc<EntrySealedCommitted>],
150        cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
151        _me: &BatchModifyEvent,
152    ) -> Result<(), OperationError> {
153        cand.iter_mut().try_for_each(apply_gidnumber)
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::{
160        GID_REGULAR_USER_MAX, GID_REGULAR_USER_MIN, GID_UNUSED_A_MAX, GID_UNUSED_A_MIN,
161        GID_UNUSED_B_MAX, GID_UNUSED_B_MIN, GID_UNUSED_C_MIN, GID_UNUSED_D_MAX,
162    };
163    use crate::prelude::*;
164
165    #[qs_test]
166    async fn test_gidnumber_generate(server: &QueryServer) {
167        let mut server_txn = server.write(duration_from_epoch_now()).await.expect("txn");
168
169        {
171            let user_a_uuid = uuid!("83a0927f-3de1-45ec-bea0-2f7b997ef244");
172            let op_result = server_txn.internal_create(vec![entry_init!(
173                (Attribute::Class, EntryClass::Account.to_value()),
174                (Attribute::Class, EntryClass::PosixAccount.to_value()),
175                (Attribute::Spn, Value::new_iname("testperson_1")),
176                (Attribute::Uuid, Value::Uuid(user_a_uuid)),
177                (Attribute::Description, Value::new_utf8s("testperson")),
178                (Attribute::DisplayName, Value::new_utf8s("testperson"))
179            )]);
180
181            assert!(op_result.is_ok());
182
183            let user_a = server_txn
184                .internal_search_uuid(user_a_uuid)
185                .expect("Unable to access user");
186
187            let user_a_uid = user_a
188                .get_ava_single_uint32(Attribute::GidNumber)
189                .expect("gidnumber not present on account");
190
191            assert_eq!(user_a_uid, 0x797ef244);
192        }
193
194        let user_b_uuid = uuid!("d90fb0cb-6785-4f36-94cb-e364d9c13255");
196        {
197            let op_result = server_txn.internal_create(vec![entry_init!(
198                (Attribute::Class, EntryClass::Account.to_value()),
199                (Attribute::Class, EntryClass::PosixAccount.to_value()),
200                (Attribute::Spn, Value::new_iname("testperson_2")),
201                (Attribute::Uuid, Value::Uuid(user_b_uuid)),
202                (Attribute::GidNumber, Value::Uint32(10001)),
203                (Attribute::Description, Value::new_utf8s("testperson")),
204                (Attribute::DisplayName, Value::new_utf8s("testperson"))
205            )]);
206
207            assert!(op_result.is_ok());
208
209            let user_b = server_txn
210                .internal_search_uuid(user_b_uuid)
211                .expect("Unable to access user");
212
213            let user_b_uid = user_b
214                .get_ava_single_uint32(Attribute::GidNumber)
215                .expect("gidnumber not present on account");
216
217            assert_eq!(user_b_uid, 10001);
218        }
219
220        {
222            let modlist = modlist!([m_purge(Attribute::GidNumber)]);
223            server_txn
224                .internal_modify_uuid(user_b_uuid, &modlist)
225                .expect("Unable to modify user");
226
227            let user_b = server_txn
228                .internal_search_uuid(user_b_uuid)
229                .expect("Unable to access user");
230
231            let user_b_uid = user_b
232                .get_ava_single_uint32(Attribute::GidNumber)
233                .expect("gidnumber not present on account");
234
235            assert_eq!(user_b_uid, 0x79c13255);
236        }
237
238        let user_c_uuid = uuid!("0d5086b0-74f9-4518-92b4-89df0c55971b");
239        {
242            let op_result = server_txn.internal_create(vec![entry_init!(
243                (Attribute::Class, EntryClass::Account.to_value()),
244                (Attribute::Class, EntryClass::Person.to_value()),
245                (Attribute::Name, Value::new_iname("testperson_3")),
246                (Attribute::Uuid, Value::Uuid(user_c_uuid)),
247                (Attribute::Description, Value::new_utf8s("testperson")),
248                (Attribute::DisplayName, Value::new_utf8s("testperson"))
249            )]);
250
251            assert!(op_result.is_ok());
252
253            let user_c = server_txn
254                .internal_search_uuid(user_c_uuid)
255                .expect("Unable to access user");
256
257            assert_eq!(user_c.get_ava_single_uint32(Attribute::GidNumber), None);
258
259            let modlist = modlist!([m_pres(
260                Attribute::Class,
261                &EntryClass::PosixAccount.to_value()
262            )]);
263            server_txn
264                .internal_modify_uuid(user_c_uuid, &modlist)
265                .expect("Unable to modify user");
266
267            let user_c = server_txn
268                .internal_search_uuid(user_c_uuid)
269                .expect("Unable to access user");
270
271            let user_c_uid = user_c
272                .get_ava_single_uint32(Attribute::GidNumber)
273                .expect("gidnumber not present on account");
274
275            assert_eq!(user_c_uid, 0x7c55971b);
276        }
277
278        let user_d_uuid = uuid!("36dc9010-d80c-404b-b5ba-8f66657c2f1d");
279        {
282            let op_result = server_txn.internal_create(vec![entry_init!(
283                (Attribute::Class, EntryClass::Account.to_value()),
284                (Attribute::Class, EntryClass::Person.to_value()),
285                (Attribute::Name, Value::new_iname("testperson_4")),
286                (Attribute::Uuid, Value::Uuid(user_d_uuid)),
287                (Attribute::Description, Value::new_utf8s("testperson")),
288                (Attribute::DisplayName, Value::new_utf8s("testperson"))
289            )]);
290
291            assert!(op_result.is_ok());
292
293            let user_d = server_txn
294                .internal_search_uuid(user_d_uuid)
295                .expect("Unable to access user");
296
297            assert_eq!(user_d.get_ava_single_uint32(Attribute::GidNumber), None);
298
299            let modlist = modlist!([m_pres(
300                Attribute::Class,
301                &EntryClass::PosixAccount.to_value()
302            )]);
303            server_txn
304                .internal_modify_uuid(user_d_uuid, &modlist)
305                .expect("Unable to modify user");
306
307            let user_d = server_txn
308                .internal_search_uuid(user_d_uuid)
309                .expect("Unable to access user");
310
311            let user_d_uid = user_d
312                .get_ava_single_uint32(Attribute::GidNumber)
313                .expect("gidnumber not present on account");
314
315            assert_eq!(user_d_uid, 0x757c2f1d);
316        }
317
318        let user_e_uuid = uuid!("a6dc0d68-9c7a-4dad-b1e2-f6274b691373");
319        {
322            let op_result = server_txn.internal_create(vec![entry_init!(
323                (Attribute::Class, EntryClass::Account.to_value()),
324                (Attribute::Class, EntryClass::Person.to_value()),
325                (Attribute::Name, Value::new_iname("testperson_5")),
326                (Attribute::Uuid, Value::Uuid(user_e_uuid)),
327                (Attribute::Description, Value::new_utf8s("testperson")),
328                (Attribute::DisplayName, Value::new_utf8s("testperson"))
329            )]);
330
331            assert!(op_result.is_ok());
332
333            let user_e = server_txn
334                .internal_search_uuid(user_e_uuid)
335                .expect("Unable to access user");
336
337            assert_eq!(user_e.get_ava_single_uint32(Attribute::GidNumber), None);
338
339            let modlist = modlist!([
340                m_pres(Attribute::Class, &EntryClass::PosixAccount.to_value()),
341                m_pres(Attribute::GidNumber, &Value::Uint32(10002))
342            ]);
343            server_txn
344                .internal_modify_uuid(user_e_uuid, &modlist)
345                .expect("Unable to modify user");
346
347            let user_e = server_txn
348                .internal_search_uuid(user_e_uuid)
349                .expect("Unable to access user");
350
351            let user_e_uid = user_e
352                .get_ava_single_uint32(Attribute::GidNumber)
353                .expect("gidnumber not present on account");
354
355            assert_eq!(user_e_uid, 10002);
356        }
357
358        let user_f_uuid = uuid!("33afc396-2434-47e5-b143-05176148b50e");
360        {
363            let op_result = server_txn.internal_create(vec![entry_init!(
364                (Attribute::Class, EntryClass::Account.to_value()),
365                (Attribute::Class, EntryClass::Person.to_value()),
366                (Attribute::Name, Value::new_iname("testperson_6")),
367                (Attribute::Uuid, Value::Uuid(user_f_uuid)),
368                (Attribute::Description, Value::new_utf8s("testperson")),
369                (Attribute::DisplayName, Value::new_utf8s("testperson"))
370            )]);
371
372            assert!(op_result.is_ok());
373
374            for id in [
375                0,
376                500,
377                GID_REGULAR_USER_MIN - 1,
378                GID_REGULAR_USER_MAX + 1,
379                GID_UNUSED_A_MIN - 1,
380                GID_UNUSED_A_MAX + 1,
381                GID_UNUSED_B_MIN - 1,
382                GID_UNUSED_B_MAX + 1,
383                GID_UNUSED_C_MIN - 1,
384                GID_UNUSED_D_MAX + 1,
385                u32::MAX,
386            ] {
387                let modlist = modlist!([
388                    m_pres(Attribute::Class, &EntryClass::PosixAccount.to_value()),
389                    m_pres(Attribute::GidNumber, &Value::Uint32(id))
390                ]);
391                let op_result = server_txn.internal_modify_uuid(user_f_uuid, &modlist);
392
393                trace!(?id);
394                assert_eq!(op_result, Err(OperationError::PL0001GidOverlapsSystemRange));
395            }
396        }
397
398        assert!(server_txn.commit().is_ok());
399    }
400}