onc_rpc/auth/
flavor.rs

1use std::{
2    convert::TryFrom,
3    io::{Cursor, Write},
4};
5
6use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
7
8use crate::{auth::AuthUnixParams, Error, Opaque};
9
10const AUTH_NONE: u32 = 0;
11const AUTH_UNIX: u32 = 1;
12const AUTH_SHORT: u32 = 2;
13
14/// A set of basic auth flavor types
15/// [described](https://tools.ietf.org/html/rfc5531#section-8.2) in RFC 5531.
16///
17/// The deprecated `AUTH_DH` is not supported, nor is GSS.
18#[non_exhaustive]
19#[derive(Debug, PartialEq, Clone)]
20pub enum AuthFlavor<T>
21where
22    T: AsRef<[u8]>,
23{
24    /// `AUTH_NONE` with the opaque data the spec allows to be included
25    /// (typically `None`).
26    ///
27    /// The provided opaque auth payload must not exceed 200 bytes in length.
28    AuthNone(Option<T>),
29
30    /// `AUTH_UNIX` and the fields it contains.
31    AuthUnix(AuthUnixParams<T>),
32
33    /// `AUTH_SHORT` and its opaque identifier
34    ///
35    /// The provided opaque auth payload must not exceed 200 bytes in length.
36    AuthShort(T),
37
38    /// An authentication credential unknown to this library, but possibly valid
39    /// and acceptable by the server.
40    Unknown {
41        /// The discriminator for this undefined auth type.
42        id: u32,
43
44        /// The opaque data contained within the this flavour.
45        ///
46        /// This payload must not exceed 200 bytes in length.
47        data: T,
48    },
49}
50
51impl<'a> AuthFlavor<&'a [u8]> {
52    pub(crate) fn from_cursor(r: &mut Cursor<&'a [u8]>) -> Result<Self, Error> {
53        // Read the auth type
54        let flavor = r.read_u32::<BigEndian>()?;
55
56        let flavor = match flavor {
57            AUTH_NONE => AuthFlavor::new_none(r)?,
58            AUTH_UNIX => AuthFlavor::new_unix(r)?,
59            AUTH_SHORT => AuthFlavor::new_short(r)?,
60            // 3 => AuthFlavor::AuthDH,
61            // 6 => AuthFlavor::RpcSecGSS,
62            v => AuthFlavor::Unknown {
63                id: v,
64                data: Opaque::from_wire(r, 200)?.into_inner(),
65            },
66        };
67
68        Ok(flavor)
69    }
70
71    fn new_none(r: &mut Cursor<&'a [u8]>) -> Result<Self, Error> {
72        let payload = Opaque::from_wire(r, 200)?.into_inner();
73        if payload.is_empty() {
74            return Ok(AuthFlavor::AuthNone(None));
75        }
76
77        Ok(AuthFlavor::AuthNone(Some(payload)))
78    }
79
80    fn new_unix(r: &mut Cursor<&'a [u8]>) -> Result<Self, Error> {
81        // TODO(dom): move this into callee
82        let len = r.read_u32::<BigEndian>()?;
83        if len > 200 {
84            return Err(Error::InvalidLength);
85        }
86
87        Ok(AuthFlavor::AuthUnix(AuthUnixParams::from_cursor(r, len)?))
88    }
89
90    fn new_short(r: &mut Cursor<&'a [u8]>) -> Result<Self, Error> {
91        Ok(AuthFlavor::AuthShort(
92            Opaque::from_wire(r, 200)?.into_inner(),
93        ))
94    }
95}
96
97impl<T> AuthFlavor<T>
98where
99    T: AsRef<[u8]>,
100{
101    /// Serialises this auth flavor and writes it into buf.
102    ///
103    /// # Panics
104    ///
105    /// Panics if an associated byte payload is provided that exceeds 200 bytes.
106    pub fn serialise_into<W: Write>(&self, mut buf: W) -> Result<(), std::io::Error> {
107        buf.write_u32::<BigEndian>(self.id())?;
108
109        // Validate the payload length.
110        assert!(self.associated_data_len() <= 200);
111
112        // Write the actual auth data
113        match self {
114            // Opaque payloads serialise their length prefix internally.
115            Self::AuthNone(Some(data)) | Self::AuthShort(data) | Self::Unknown { data, .. } => {
116                Opaque::from_user_payload(data).serialise_into(&mut buf)
117            }
118            // No payload has a length of 0.
119            Self::AuthNone(None) => {
120                buf.write_u32::<BigEndian>(0)?;
121                Ok(())
122            }
123            // Auth unix payloads have their length serialised by the caller.
124            Self::AuthUnix(p) => {
125                buf.write_u32::<BigEndian>(p.serialised_len())?;
126                p.serialise_into(buf)
127            }
128        }
129    }
130
131    /// Returns the ID value used to identify the variant in the wire protocol.
132    pub fn id(&self) -> u32 {
133        match self {
134            Self::AuthNone(_) => AUTH_NONE,
135            Self::AuthUnix(_) => AUTH_UNIX,
136            Self::AuthShort(_) => AUTH_SHORT,
137            Self::Unknown { id, data: _ } => *id,
138        }
139    }
140
141    /// Returns the byte length of the associated auth data, if any.
142    pub fn associated_data_len(&self) -> u32 {
143        match self {
144            Self::AuthNone(Some(d)) => d.as_ref().len() as u32,
145            Self::AuthNone(None) => 0,
146            Self::AuthUnix(p) => p.associated_data_len(),
147            Self::AuthShort(d) => d.as_ref().len() as u32,
148            Self::Unknown { id: _id, data } => data.as_ref().len() as u32,
149        }
150    }
151
152    /// Returns the on-wire length of this auth flavor once serialised,
153    /// including discriminator and length values.
154    pub fn serialised_len(&self) -> u32 {
155        let mut l = 0;
156
157        // Flavor discriminator
158        l += 4;
159
160        // Add the flavor size
161        l += match self {
162            #[allow(clippy::identity_op)]
163            Self::AuthNone(None) => {
164                // length prefix u32 + data length
165                4 + 0
166            }
167            Self::AuthUnix(ref p) => 4 + p.serialised_len(),
168            Self::Unknown { data, .. } | Self::AuthShort(data) | Self::AuthNone(Some(data)) => {
169                Opaque::from_user_payload(data).serialised_len()
170            }
171        };
172
173        l
174    }
175}
176
177impl<'a> TryFrom<&'a [u8]> for AuthFlavor<&'a [u8]> {
178    type Error = Error;
179
180    fn try_from(v: &'a [u8]) -> Result<Self, Self::Error> {
181        let mut c = Cursor::new(v);
182        AuthFlavor::from_cursor(&mut c)
183    }
184}
185
186#[cfg(feature = "bytes")]
187impl TryFrom<crate::Bytes> for AuthFlavor<crate::Bytes> {
188    type Error = Error;
189
190    fn try_from(mut v: crate::Bytes) -> Result<Self, Self::Error> {
191        use crate::bytes_ext::BytesReaderExt;
192
193        let flavor = v.try_u32()?;
194        let auth_data = v.try_array(200)?;
195
196        let flavor = match flavor {
197            AUTH_NONE if auth_data.is_empty() => Self::AuthNone(None),
198            AUTH_NONE => Self::AuthNone(Some(auth_data)),
199            AUTH_UNIX => {
200                // Prevent malformed messages from including trailing data in
201                // the AUTH_UNIX structure - the deserialised structure should
202                // fully consume the opaque data associated with the AUTH_UNIX
203                // variant.
204                let should_consume = auth_data.len();
205                let params = AuthUnixParams::try_from(auth_data)?;
206                if params.serialised_len() as usize != should_consume {
207                    return Err(Error::InvalidAuthData);
208                }
209                Self::AuthUnix(params)
210            }
211            AUTH_SHORT => Self::AuthShort(auth_data),
212            // 3 => AuthFlavor::AuthDH,
213            // 6 => AuthFlavor::RpcSecGSS,
214            id => Self::Unknown {
215                id,
216                data: auth_data,
217            },
218        };
219
220        Ok(flavor)
221    }
222}
223
224#[cfg(test)]
225mod tests {
226    use std::convert::TryInto;
227
228    use hex_literal::hex;
229
230    use super::*;
231
232    #[test]
233    fn test_auth_unix_unaligned_machinename<'a>() {
234        #[rustfmt::skip]
235        // Credentials
236        //     Flavor: AUTH_UNIX (1)
237        //     Length: 36
238        //     Stamp: 0x00000000
239        //     Machine Name: LAPTOP-1QQBPDGM
240        //         length: 15
241        //         contents: LAPTOP-1QQBPDGM
242        //     UID: 0
243        //     GID: 0
244        //     Auxiliary GIDs (0)
245        const RAW: [u8; 44] = hex!(
246            "0000000100000024000000000000000f4c4150544f502d315151425044474d00000000000000000000000000"
247        );
248
249        let f: AuthFlavor<&'a [u8]> = RAW.as_ref().try_into().expect("failed to parse message");
250        assert_eq!(f.serialised_len(), 44);
251        assert_eq!(f.id(), AUTH_UNIX);
252        assert_eq!(f.associated_data_len(), 27);
253
254        let params = match f {
255            AuthFlavor::AuthUnix(ref p) => p,
256            _ => panic!("wrong auth"),
257        };
258
259        assert_eq!(params.uid(), 0);
260
261        let mut c = Cursor::new(Vec::new());
262        f.serialise_into(&mut c).expect("serialise failed");
263
264        let buf = c.into_inner();
265        assert_eq!(buf.as_slice(), RAW.as_ref());
266    }
267
268    #[test]
269    fn test_auth_unix<'a>() {
270        #[rustfmt::skip]
271        // Credentials
272        //     Flavor: AUTH_UNIX (1)
273        //     Length: 84
274        //     Stamp: 0x00000000
275        //     Machine Name: <EMPTY>
276        //         length: 0
277        //         contents: <EMPTY>
278        //     UID: 501
279        //     GID: 20
280        //     Auxiliary GIDs (16) [501, 12, 20, 61, 79, 80, 81, 98, 701, 33, 100, 204, 250, 395, 398, 399]
281        //         GID: 501
282        //         GID: 12
283        //         GID: 20
284        //         GID: 61
285        //         GID: 79
286        //         GID: 80
287        //         GID: 81
288        //         GID: 98
289        //         GID: 701
290        //         GID: 33
291        //         GID: 100
292        //         GID: 204
293        //         GID: 250
294        //         GID: 395
295        //         GID: 398
296        //         GID: 399
297        const RAW: [u8; 92] = hex!(
298            "00000001000000540000000000000000000001f50000001400000010000001f500
299            00000c000000140000003d0000004f000000500000005100000062000002bd00000
300            02100000064000000cc000000fa0000018b0000018e0000018f"
301        );
302
303        let f: AuthFlavor<&'a [u8]> = RAW.as_ref().try_into().expect("failed to parse message");
304        assert_eq!(f.serialised_len(), 92);
305        assert_eq!(f.id(), AUTH_UNIX);
306        assert_eq!(f.associated_data_len(), 92 - 4 - 4 - 4 - 4); // - auth flavor - auth size - gids length - name length
307
308        let params = match f {
309            AuthFlavor::AuthUnix(ref p) => p,
310            _ => panic!("wrong auth"),
311        };
312
313        assert_eq!(params.uid(), 501);
314
315        let mut c = Cursor::new(Vec::new());
316        f.serialise_into(&mut c).expect("serialise failed");
317
318        let buf = c.into_inner();
319        assert_eq!(buf.as_slice(), RAW.as_ref());
320    }
321
322    #[test]
323    fn test_auth_none<'a>() {
324        const RAW: [u8; 92] = hex!(
325            "
326            00 00 00 00
327            00 00 00 54
328            0000000000000000000001f50000001400000010000001f50000000c00000014000
329            0003d0000004f000000500000005100000062000002bd0000002100000064000000
330            cc000000fa0000018b0000018e0000018f"
331        );
332
333        let f: AuthFlavor<&'a [u8]> = RAW.as_ref().try_into().expect("failed to parse message");
334        assert_eq!(f.serialised_len(), 92);
335        assert_eq!(f.id(), AUTH_NONE);
336        assert_eq!(f.associated_data_len(), 92 - 4 - 4);
337
338        let data = match f {
339            AuthFlavor::AuthNone(Some(ref p)) => p,
340            _ => panic!("wrong auth"),
341        };
342
343        assert_eq!(data.len(), f.associated_data_len() as usize);
344    }
345
346    #[test]
347    fn test_auth_short<'a>() {
348        const RAW: [u8; 92] = hex!(
349            "
350            00 00 00 02
351            00 00 00 54
352            0000000000000000000001f50000001400000010000001f50000000c00000014000
353            0003d0000004f000000500000005100000062000002bd0000002100000064000000
354            cc000000fa0000018b0000018e0000018f"
355        );
356
357        let f: AuthFlavor<&'a [u8]> = RAW.as_ref().try_into().expect("failed to parse message");
358        assert_eq!(f.serialised_len(), 92);
359        assert_eq!(f.id(), AUTH_SHORT);
360        assert_eq!(f.associated_data_len(), 92 - 4 - 4);
361
362        let data = match f {
363            AuthFlavor::AuthShort(ref p) => p,
364            _ => panic!("wrong auth"),
365        };
366
367        assert_eq!(data.len(), f.associated_data_len() as usize);
368    }
369
370    #[test]
371    fn test_auth_unknown<'a>() {
372        const RAW: [u8; 92] = hex!(
373            "
374            00 00 00 FF
375            00 00 00 54
376            0000000000000000000001f50000001400000010000001f50000000c00000014000
377            0003d0000004f000000500000005100000062000002bd0000002100000064000000
378            cc000000fa0000018b0000018e0000018f"
379        );
380
381        let f: AuthFlavor<&'a [u8]> = RAW.as_ref().try_into().expect("failed to parse message");
382        assert_eq!(f.serialised_len(), 92);
383        assert_eq!(f.id(), 255);
384        assert_eq!(f.associated_data_len(), 92 - 4 - 4);
385
386        let (id, data) = match f {
387            AuthFlavor::Unknown { id, data } => (id, data),
388            _ => panic!("wrong auth"),
389        };
390
391        assert_eq!(id, f.id());
392        assert_eq!(data.len(), f.associated_data_len() as usize);
393    }
394}