onc_rpc/auth/
unix_params.rs

1use std::{
2    io::{Cursor, Write},
3    iter::FromIterator,
4    ops::Deref,
5};
6
7use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
8
9use crate::{Error, Opaque};
10
11const MAX_GIDS: usize = 16;
12const MAX_MACHINE_NAME_LEN: u32 = 255;
13
14/// A variable length array of GID values with a maximum capacity of
15/// [`MAX_GIDS`].
16#[derive(Clone, PartialEq, Default)]
17struct Gids {
18    /// The GID values container.
19    values: [u32; MAX_GIDS],
20
21    /// 1-indexed length (number of elements) in `values`.
22    len: u8,
23}
24
25impl Deref for Gids {
26    type Target = [u32];
27
28    fn deref(&self) -> &Self::Target {
29        &self.values[..self.len as usize]
30    }
31}
32
33impl std::fmt::Debug for Gids {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        self.deref().fmt(f)
36    }
37}
38
39impl FromIterator<u32> for Gids {
40    fn from_iter<T: IntoIterator<Item = u32>>(iter: T) -> Self {
41        let mut values = [0; MAX_GIDS];
42        let mut len = 0;
43
44        // Populate up to MAX_GIDS number of elements
45        for v in iter.into_iter() {
46            // Never silently drop extra values.
47            assert!(len < MAX_GIDS);
48
49            values[len] = v;
50            len += 1;
51        }
52
53        Self {
54            values,
55            len: len as u8,
56        }
57    }
58}
59
60/// `AuthUnixParams` represents the structures referred to as both `AUTH_UNIX`
61/// and `AUTH_SYS` in the various RFCs, used to identify the client as a Unix
62/// user.
63///
64/// The structure is implemented as specified in `APPENDIX A` of
65/// [RFC1831](https://tools.ietf.org/html/rfc1831).
66///
67/// The client-provided machine name is limited to, at most, 16 bytes. If
68/// additional group IDs ([`AuthUnixParams::gids()`]) are provided, the protocol
69/// allows for at most 16 values.
70///
71/// These values are trivial to forge and provide no actual security.
72#[derive(Debug, PartialEq, Clone)]
73pub struct AuthUnixParams<T>
74where
75    T: AsRef<[u8]>,
76{
77    stamp: u32,
78    machine_name: Opaque<T>,
79    uid: u32,
80    gid: u32,
81    gids: Gids,
82}
83
84impl<'a> AuthUnixParams<&'a [u8]> {
85    /// Constructs a new `AuthUnixParams` by parsing the wire format read from
86    /// `r`, validating it has read exactly `expected_len` number of bytes.
87    ///
88    /// `from_cursor` advances the position of `r` to the end of the `AUTH_UNIX`
89    /// structure.
90    pub(crate) fn from_cursor(r: &mut Cursor<&'a [u8]>, expected_len: u32) -> Result<Self, Error> {
91        // Get the start length the parser can validate it read the expected
92        // amount of data at the end of the function
93        let start_pos = r.position();
94
95        // Read the stamp
96        let stamp = r.read_u32::<BigEndian>()?;
97
98        // Read the string without copying
99        let machine_name = Opaque::from_wire(&mut *r, MAX_MACHINE_NAME_LEN as _)?;
100
101        // UID & GID
102        let uid = r.read_u32::<BigEndian>()?;
103        let gid = r.read_u32::<BigEndian>()?;
104
105        // Gids
106        let gids_count = r.read_u32::<BigEndian>()? as usize;
107        let gids = match gids_count {
108            0 => Gids::default(),
109            c if c <= 16 => (0..c)
110                .map(|_| r.read_u32::<BigEndian>())
111                .collect::<Result<Gids, _>>()?,
112            _ => return Err(Error::InvalidAuthData),
113        };
114
115        // Validate the parser read the expected amount of data to construct
116        // this type
117        if (r.position() - start_pos) != expected_len as u64 {
118            return Err(Error::InvalidAuthData);
119        }
120
121        Ok(AuthUnixParams {
122            stamp,
123            machine_name,
124            uid,
125            gid,
126            gids,
127        })
128    }
129}
130
131impl<T> AuthUnixParams<T>
132where
133    T: AsRef<[u8]>,
134{
135    /// Initialise a new `AuthUnixParams` instance containing the specified unix
136    /// account identifiers.
137    ///
138    /// # Panics
139    ///
140    /// Panics if the machine name exceeds 16 bytes, or `gids` contains more
141    /// than 16 elements.
142    pub fn new(
143        stamp: u32,
144        machine_name: T,
145        uid: u32,
146        gid: u32,
147        gids: impl IntoIterator<Item = u32>,
148    ) -> Self {
149        assert!(machine_name.as_ref().len() <= 16);
150
151        Self {
152            stamp,
153            machine_name: Opaque::from_user_payload(machine_name),
154            uid,
155            gid,
156            gids: gids.into_iter().collect::<Gids>(),
157        }
158    }
159
160    /// Serialises this `AuthUnixParams` into `buf`, advancing the cursor
161    /// position by [`AuthUnixParams::serialised_len()`] bytes.
162    pub fn serialise_into<W: Write>(&self, mut buf: W) -> Result<(), std::io::Error> {
163        buf.write_u32::<BigEndian>(self.stamp)?;
164        self.machine_name.serialise_into(&mut buf)?;
165        buf.write_u32::<BigEndian>(self.uid)?;
166        buf.write_u32::<BigEndian>(self.gid)?;
167
168        // Gids array length prefix
169        buf.write_u32::<BigEndian>(self.gids.deref().len() as u32)?;
170
171        // Gids values
172        for g in &*self.gids {
173            buf.write_u32::<BigEndian>(*g)?;
174        }
175        Ok(())
176    }
177
178    /// An arbitrary ID generated by the caller.
179    pub fn stamp(&self) -> u32 {
180        self.stamp
181    }
182
183    /// The hostname of the caller's machine.
184    pub fn machine_name(&self) -> &[u8] {
185        self.machine_name.as_ref()
186    }
187
188    /// The hostname of the caller's machine as a reference to a UTF8 string.
189    ///
190    /// # Panics
191    ///
192    /// If the machine name cannot be expressed as a valid UTF8 string, this
193    /// method panics.
194    pub fn machine_name_str(&self) -> &str {
195        std::str::from_utf8(self.machine_name.as_ref()).unwrap()
196    }
197
198    /// The caller's Unix user ID.
199    pub fn uid(&self) -> u32 {
200        self.uid
201    }
202
203    /// The caller's primary Unix group ID.
204    pub fn gid(&self) -> u32 {
205        self.gid
206    }
207
208    /// Returns a copy of the `gids` array, a set of Unix group IDs the caller
209    /// is a member of.
210    pub fn gids(&self) -> Option<&[u32]> {
211        if self.gids.len == 0 {
212            return None;
213        }
214        Some(&*self.gids)
215    }
216
217    /// Returns the on-wire length of this message once serialised, including
218    /// the message header.
219    pub fn serialised_len(&self) -> u32 {
220        // uid, gid, stamp
221        let mut l = std::mem::size_of::<u32>() * 3;
222
223        // machine_name length
224        l += self.machine_name.serialised_len() as usize;
225
226        // gids length prefix u32 + values
227        l += (self.gids.deref().len() + 1) * std::mem::size_of::<u32>();
228
229        l as u32
230    }
231
232    /// Returns the byte sizes of the fields within this data (excluding
233    /// serialisation overhead).
234    pub(crate) fn associated_data_len(&self) -> u32 {
235        // uid, gid, stamp
236        let mut l = std::mem::size_of::<u32>() * 3;
237
238        // machine_name without length prefix
239        l += self.machine_name.len();
240
241        // gids without length prefix
242        l += std::mem::size_of_val(self.gids.deref());
243
244        l as u32
245    }
246}
247
248#[cfg(feature = "bytes")]
249impl TryFrom<crate::Bytes> for AuthUnixParams<crate::Bytes> {
250    type Error = Error;
251
252    fn try_from(mut v: crate::Bytes) -> Result<Self, Self::Error> {
253        use crate::bytes_ext::BytesReaderExt;
254
255        let stamp = v.try_u32()?;
256
257        let name = v.try_array(MAX_MACHINE_NAME_LEN as _)?;
258        let uid = v.try_u32()?;
259        let gid = v.try_u32()?;
260
261        let gids_count = v.try_u32()? as usize;
262        let gids = match gids_count {
263            0 => Gids::default(),
264            c if c <= 16 => (0..c).map(|_| v.try_u32()).collect::<Result<Gids, _>>()?,
265            _ => return Err(Error::InvalidAuthData),
266        };
267
268        Ok(Self {
269            stamp,
270            machine_name: Opaque::from_user_payload(name),
271            uid,
272            gid,
273            gids,
274        })
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use hex_literal::hex;
281
282    #[cfg(feature = "bytes")]
283    use crate::Bytes;
284
285    use super::*;
286
287    #[test]
288    fn test_serialise_deserialise() {
289        let gids = [
290            501, 12, 20, 61, 79, 80, 81, 98, 701, 33, 100, 204, 250, 395, 398, 399,
291        ];
292        let params = AuthUnixParams::new(0, b"".as_ref(), 501, 20, gids);
293
294        let mut buf = Cursor::new(Vec::new());
295        params
296            .serialise_into(&mut buf)
297            .expect("failed to serialise");
298
299        #[rustfmt::skip]
300        // Known good wire value trimmed of flavor + length bytes.
301        //
302        // Credentials
303        //     Flavor: AUTH_UNIX (1)
304        //     Length: 84
305        //     Stamp: 0x00000000
306        //     Machine Name: <EMPTY>
307        //         length: 0
308        //         contents: <EMPTY>
309        //     UID: 501
310        //     GID: 20
311        //     Auxiliary GIDs (16) [501, 12, 20, 61, 79, 80, 81, 98, 701, 33, 100, 204, 250, 395, 398, 399]
312        //         GID: 501
313        //         GID: 12
314        //         GID: 20
315        //         GID: 61
316        //         GID: 79
317        //         GID: 80
318        //         GID: 81
319        //         GID: 98
320        //         GID: 701
321        //         GID: 33
322        //         GID: 100
323        //         GID: 204
324        //         GID: 250
325        //         GID: 395
326        //         GID: 398
327        //         GID: 399
328        //
329        let want = hex!(
330            "0000000000000000000001f50000001400000010000001f50000000c0000001400
331            00003d0000004f000000500000005100000062000002bd000000210000006400000
332            0cc000000fa0000018b0000018e0000018f"
333        );
334
335        let buf = buf.into_inner();
336        assert_eq!(want.len(), buf.len());
337        assert_eq!(want.as_ref(), buf.as_slice());
338
339        let mut c = Cursor::new(want.as_ref());
340        let s = AuthUnixParams::from_cursor(&mut c, 84).expect("deserialise failed");
341
342        assert_eq!(s.serialised_len(), 84);
343        assert_eq!(params, s);
344    }
345
346    #[test]
347    fn test_empty() {
348        // Known good wire value trimmed of flavor + length bytes.
349        //
350        // Credentials
351        //     Flavor: AUTH_UNIX (1)
352        //     Length: 24
353        //     Stamp: 0x00000000
354        //     Machine Name: <EMPTY>
355        //         length: 0
356        //         contents: <EMPTY>
357        //     UID: 0
358        //     GID: 0
359        //     Auxiliary GIDs (1) [0]
360        //         GID: 0
361        let want = hex!("000000000000000000000000000000000000000100000000");
362        let mut c = Cursor::new(want.as_ref());
363
364        let s = AuthUnixParams::from_cursor(&mut c, 24).expect("deserialise failed");
365
366        assert_eq!(s.stamp(), 0);
367        assert_eq!(s.machine_name_str(), "");
368        assert_eq!(s.uid(), 0);
369        assert_eq!(s.gid(), 0);
370        assert_eq!(s.gids(), Some([0].as_slice()));
371        assert_eq!(s.serialised_len(), 24);
372
373        let mut buf = Cursor::new(Vec::new());
374        s.serialise_into(&mut buf).expect("failed to serialise");
375
376        let buf = buf.into_inner();
377        assert_eq!(want.len(), buf.len());
378        assert_eq!(want.as_ref(), buf.as_slice());
379    }
380
381    #[test]
382    #[cfg(feature = "bytes")]
383    fn test_deserialise_bytes() {
384        #[rustfmt::skip]
385        // Known good wire value trimmed of flavor + length bytes.
386        //
387        // Credentials
388        //     Flavor: AUTH_UNIX (1)
389        //     Length: 84
390        //     Stamp: 0x00000000
391        //     Machine Name: <EMPTY>
392        //         length: 0
393        //         contents: <EMPTY>
394        //     UID: 501
395        //     GID: 20
396        //     Auxiliary GIDs (16) [501, 12, 20, 61, 79, 80, 81, 98, 701, 33, 100, 204, 250, 395, 398, 399]
397        //         GID: 501
398        //         GID: 12
399        //         GID: 20
400        //         GID: 61
401        //         GID: 79
402        //         GID: 80
403        //         GID: 81
404        //         GID: 98
405        //         GID: 701
406        //         GID: 33
407        //         GID: 100
408        //         GID: 204
409        //         GID: 250
410        //         GID: 395
411        //         GID: 398
412        //         GID: 399
413        //
414        let want = hex!(
415            "0000000000000000000001f50000001400000010000001f50000000c0000001400
416            00003d0000004f000000500000005100000062000002bd000000210000006400000
417            0cc000000fa0000018b0000018e0000018f"
418        );
419        let static_want: &'static [u8] = Box::leak(Box::new(want));
420
421        let got =
422            AuthUnixParams::try_from(Bytes::from(static_want)).expect("failed to deserialise");
423
424        assert_eq!(got.stamp(), 0);
425        assert_eq!(got.machine_name_str(), "");
426        assert_eq!(got.uid(), 501);
427        assert_eq!(got.gid(), 20);
428        assert_eq!(
429            got.gids(),
430            Some(
431                [501, 12, 20, 61, 79, 80, 81, 98, 701, 33, 100, 204, 250, 395, 398, 399].as_slice()
432            )
433        );
434        assert_eq!(got.serialised_len(), 84);
435    }
436
437    #[test]
438    #[cfg(feature = "bytes")]
439    fn test_empty_bytes() {
440        // Known good wire value trimmed of flavor + length bytes.
441        //
442        // Credentials
443        //     Flavor: AUTH_UNIX (1)
444        //     Length: 24
445        //     Stamp: 0x00000000
446        //     Machine Name: <EMPTY>
447        //         length: 0
448        //         contents: <EMPTY>
449        //     UID: 0
450        //     GID: 0
451        //     Auxiliary GIDs (1) [0]
452        //         GID: 0
453        let want = hex!("000000000000000000000000000000000000000100000000");
454        let static_want: &'static [u8] = Box::leak(Box::new(want));
455
456        let s = AuthUnixParams::try_from(Bytes::from(static_want)).expect("deserialise failed");
457
458        assert_eq!(s.stamp(), 0);
459        assert_eq!(s.machine_name_str(), "");
460        assert_eq!(s.uid(), 0);
461        assert_eq!(s.gid(), 0);
462        assert_eq!(s.gids(), Some([0].as_slice()));
463        assert_eq!(s.serialised_len(), 24);
464
465        let mut buf = Cursor::new(Vec::new());
466        s.serialise_into(&mut buf).expect("failed to serialise");
467
468        let buf = buf.into_inner();
469        assert_eq!(want.len(), buf.len());
470        assert_eq!(want.as_ref(), buf.as_slice());
471    }
472
473    #[test]
474    #[should_panic]
475    fn test_long_machine_name_panic() {
476        AuthUnixParams::new(42, [1_u8; 256], 42, 42, None);
477    }
478
479    #[test]
480    #[should_panic]
481    fn test_long_gids_panic() {
482        AuthUnixParams::new(
483            42,
484            Opaque::from_user_payload([].as_slice()),
485            42,
486            42,
487            [
488                1_u32, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
489            ],
490        );
491    }
492}