Skip to main content

nfs_rs/nfs4/
acl.rs

1//! Common NFSv4 ACL (Access Control List) encode/decode.
2//!
3//! NFSv4 ACLs are carried as FATTR4_ACL (attribute #12) inside GETATTR/SETATTR,
4//! not as separate RPC procedures. See RFC 7530 §6.2.
5
6use bytes::{Buf, Bytes};
7
8use super::attrnum;
9use super::attrs::{decode_getattr_envelope, decode_utf8str};
10use crate::error::{NfsError, Result};
11use crate::mount::{AceFlags, AceMask, AceType, Acl, Acl41Flags, AclSupport, NfsAce, NfsAcl41};
12
13/// Translate an operation-local FATTR4_ACL rejection without caching it.
14/// ATTRNOTSUPP can depend on the object or ACL contents, while ACLSUPPORT is
15/// independently a per-filesystem set of supported ACE types.
16pub(crate) fn attrnotsupp_as_unsupported<T>(operation: &str, result: Result<T>) -> Result<T> {
17    match result {
18        Err(NfsError::Nfs4(crate::nfs4::Nfs4ErrorCode::NFS4ERR_ATTRNOTSUPP)) => {
19            Err(NfsError::Unsupported(format!(
20                "{operation} is unavailable for this request: server returned NFS4ERR_ATTRNOTSUPP"
21            )))
22        }
23        other => other,
24    }
25}
26
27/// Maximum number of ACEs we'll decode from a single response.
28/// Prevents unbounded allocation from a malformed server response.
29const MAX_ACES: usize = 8192;
30
31// ─── AceType conversion ────────────────────────────────────────────────────────
32
33impl TryFrom<u32> for AceType {
34    type Error = NfsError;
35
36    fn try_from(v: u32) -> Result<Self> {
37        match v {
38            0 => Ok(AceType::AccessAllowed),
39            1 => Ok(AceType::AccessDenied),
40            2 => Ok(AceType::SystemAudit),
41            3 => Ok(AceType::SystemAlarm),
42            _ => Err(NfsError::Xdr(format!("unknown ACE type {}", v))),
43        }
44    }
45}
46
47// ─── Decode ────────────────────────────────────────────────────────────────────
48
49/// Decode a single nfsace4 from the wire.
50/// Wire format: type(u32) + flag(u32) + access_mask(u32) + who(utf8str_mixed).
51pub(crate) fn decode_nfsace4(buf: &mut Bytes) -> Result<NfsAce> {
52    if buf.remaining() < 12 {
53        return Err(NfsError::Xdr("nfsace4 truncated".to_string()));
54    }
55    let ace_type = AceType::try_from(buf.get_u32())?;
56    let flags = AceFlags(buf.get_u32());
57    let access_mask = AceMask(buf.get_u32());
58    let who = decode_utf8str(buf)?;
59    Ok(NfsAce {
60        ace_type,
61        flags,
62        access_mask,
63        who,
64    })
65}
66
67/// Decode a variable-length array of nfsace4.
68/// Wire format: count(u32) + count * nfsace4.
69pub(crate) fn decode_acl(buf: &mut Bytes) -> Result<Acl> {
70    if buf.remaining() < 4 {
71        return Err(NfsError::Xdr("ACL count truncated".to_string()));
72    }
73    let count = buf.get_u32() as usize;
74    if count > MAX_ACES {
75        return Err(NfsError::Xdr(format!(
76            "ACL has {} entries, max {}",
77            count, MAX_ACES
78        )));
79    }
80    let mut aces = Vec::with_capacity(count);
81    for _ in 0..count {
82        aces.push(decode_nfsace4(buf)?);
83    }
84    Ok(Acl { aces })
85}
86
87/// Skip over an ACL in the attribute value stream without allocating.
88/// Used by `decode_fattr4_to_attr` when ACL appears in the bitmap but
89/// we only need standard attributes.
90pub(super) fn skip_acl(buf: &mut Bytes) -> Result<()> {
91    if buf.remaining() < 4 {
92        return Err(NfsError::Xdr("ACL count truncated".to_string()));
93    }
94    let count = buf.get_u32() as usize;
95    if count > MAX_ACES {
96        return Err(NfsError::Xdr(format!(
97            "ACL has {} entries, max {}",
98            count, MAX_ACES
99        )));
100    }
101    for _ in 0..count {
102        // nfsace4: type(4) + flag(4) + access_mask(4) + who(var)
103        if buf.remaining() < 12 {
104            return Err(NfsError::Xdr("nfsace4 truncated".to_string()));
105        }
106        buf.advance(12);
107        // Skip utf8str: len(4) + padded data
108        if buf.remaining() < 4 {
109            return Err(NfsError::Xdr("nfsace4 who length truncated".to_string()));
110        }
111        let len = buf.get_u32() as usize;
112        let padded = (len + 3) & !3;
113        if buf.remaining() < padded {
114            return Err(NfsError::Xdr("nfsace4 who data truncated".to_string()));
115        }
116        buf.advance(padded);
117    }
118    Ok(())
119}
120
121/// Skip an NFSv4.1 `nfsacl41` value: ACL flags followed by an ACE array.
122pub(super) fn skip_acl41(buf: &mut Bytes) -> Result<()> {
123    if buf.remaining() < 4 {
124        return Err(NfsError::Xdr("NFSv4.1 ACL flags truncated".to_string()));
125    }
126    buf.advance(4);
127    skip_acl(buf)
128}
129
130// ─── Encode ────────────────────────────────────────────────────────────────────
131
132/// Encode a single nfsace4 to XDR wire format.
133fn encode_nfsace4(ace: &NfsAce, buf: &mut Vec<u8>) {
134    buf.extend_from_slice(&(ace.ace_type as u32).to_be_bytes());
135    buf.extend_from_slice(&ace.flags.0.to_be_bytes());
136    buf.extend_from_slice(&ace.access_mask.0.to_be_bytes());
137    // utf8str_mixed: len(4) + data + pad
138    let who_bytes = ace.who.as_bytes();
139    buf.extend_from_slice(&(who_bytes.len() as u32).to_be_bytes());
140    buf.extend_from_slice(who_bytes);
141    let pad = (4 - who_bytes.len() % 4) % 4;
142    for _ in 0..pad {
143        buf.push(0);
144    }
145}
146
147/// Encode an ACL as a variable-length array of nfsace4.
148fn encode_acl(acl: &Acl, buf: &mut Vec<u8>) {
149    buf.extend_from_slice(&(acl.aces.len() as u32).to_be_bytes());
150    for ace in &acl.aces {
151        encode_nfsace4(ace, buf);
152    }
153}
154
155/// Encode an ACL into (attrmask, attr_vals) for use in SETATTR.
156/// Sets bit 12 (FATTR4_ACL) in word 0 of the bitmap.
157pub(crate) fn encode_setattr_acl(acl: &Acl) -> (Vec<u32>, Vec<u8>) {
158    let word0: u32 = 1 << attrnum::ACL;
159    let mut vals = Vec::new();
160    encode_acl(acl, &mut vals);
161    (vec![word0], vals)
162}
163
164pub(crate) fn encode_setattr_acl41(acl: &NfsAcl41, attribute: u32) -> (Vec<u32>, Vec<u8>) {
165    debug_assert!(matches!(attribute, attrnum::DACL | attrnum::SACL));
166    let mut values = Vec::new();
167    values.extend_from_slice(&acl.flags.0.to_be_bytes());
168    encode_acl(
169        &Acl {
170            aces: acl.aces.clone(),
171        },
172        &mut values,
173    );
174    (vec![0, 1 << (attribute - 32)], values)
175}
176
177// ─── GETATTR response decoders ─────────────────────────────────────────────────
178
179/// Parse a GETATTR response that requested FATTR4_ACL (bit 12) and decode the ACL.
180pub(crate) fn decode_getattr_acl(data: &mut Bytes) -> Result<Acl> {
181    let (bitmap, mut vals) = decode_getattr_envelope(data)?;
182    let word0 = bitmap.first().copied().unwrap_or(0);
183    if word0 & (1 << attrnum::ACL) == 0 {
184        return Err(NfsError::Xdr(
185            "server did not return FATTR4_ACL".to_string(),
186        ));
187    }
188    // Guard: attributes are encoded in bit-number order. If any bits below 12 are set,
189    // their values precede the ACL data in attr_vals and would cause a misparse.
190    if word0 & ((1 << attrnum::ACL) - 1) != 0 {
191        return Err(NfsError::Xdr(
192            "server returned unexpected attributes before FATTR4_ACL".to_string(),
193        ));
194    }
195    decode_acl(&mut vals)
196}
197
198/// Parse a GETATTR response that requested FATTR4_ACLSUPPORT (bit 13).
199pub(crate) fn decode_getattr_aclsupport(data: &mut Bytes) -> Result<AclSupport> {
200    let (bitmap, mut vals) = decode_getattr_envelope(data)?;
201    let word0 = bitmap.first().copied().unwrap_or(0);
202    if word0 & (1 << attrnum::ACLSUPPORT) == 0 {
203        return Err(NfsError::Xdr(
204            "server did not return FATTR4_ACLSUPPORT".to_string(),
205        ));
206    }
207    // Guard: if any bits below 13 are set, their values precede ACLSUPPORT data.
208    if word0 & ((1 << attrnum::ACLSUPPORT) - 1) != 0 {
209        return Err(NfsError::Xdr(
210            "server returned unexpected attributes before FATTR4_ACLSUPPORT".to_string(),
211        ));
212    }
213    if vals.remaining() < 4 {
214        return Err(NfsError::Xdr("ACLSUPPORT value truncated".to_string()));
215    }
216    Ok(AclSupport(vals.get_u32()))
217}
218
219pub(crate) fn decode_getattr_acl41(data: &mut Bytes, attribute: u32) -> Result<NfsAcl41> {
220    debug_assert!(matches!(attribute, attrnum::DACL | attrnum::SACL));
221    let (bitmap, mut vals) = decode_getattr_envelope(data)?;
222    let word1 = bitmap.get(1).copied().unwrap_or(0);
223    let bit = 1 << (attribute - 32);
224    if word1 & bit == 0 {
225        return Err(NfsError::Unsupported(format!(
226            "server does not support NFSv4.1 ACL attribute {attribute}"
227        )));
228    }
229    if vals.remaining() < 4 {
230        return Err(NfsError::Xdr("NFSv4.1 ACL flags truncated".to_string()));
231    }
232    let flags = Acl41Flags(vals.get_u32());
233    let aces = decode_acl(&mut vals)?.aces;
234    Ok(NfsAcl41 { flags, aces })
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240
241    fn make_ace(ace_type: AceType, flags: u32, mask: u32, who: &str) -> NfsAce {
242        NfsAce {
243            ace_type,
244            flags: AceFlags(flags),
245            access_mask: AceMask(mask),
246            who: who.to_string(),
247        }
248    }
249
250    fn encode_one_ace(ace: &NfsAce) -> Vec<u8> {
251        let mut buf = Vec::new();
252        encode_nfsace4(ace, &mut buf);
253        buf
254    }
255
256    #[test]
257    fn ace_type_try_from_valid() {
258        assert_eq!(AceType::try_from(0).unwrap(), AceType::AccessAllowed);
259        assert_eq!(AceType::try_from(1).unwrap(), AceType::AccessDenied);
260        assert_eq!(AceType::try_from(2).unwrap(), AceType::SystemAudit);
261        assert_eq!(AceType::try_from(3).unwrap(), AceType::SystemAlarm);
262    }
263
264    #[test]
265    fn ace_type_try_from_invalid() {
266        assert!(AceType::try_from(4).is_err());
267        assert!(AceType::try_from(999).is_err());
268    }
269
270    #[test]
271    fn roundtrip_single_ace() {
272        let ace = make_ace(AceType::AccessAllowed, 0x01, 0x1F01FF, "OWNER@");
273        let encoded = encode_one_ace(&ace);
274        let mut bytes = Bytes::from(encoded);
275        let decoded = decode_nfsace4(&mut bytes).unwrap();
276        assert_eq!(decoded, ace);
277        assert_eq!(bytes.remaining(), 0);
278    }
279
280    #[test]
281    fn rfc7530_nfsace4_literal_decodes_and_reencodes_exactly() {
282        let wire: &[u8] = &[
283            0, 0, 0, 0, // ACE4_ACCESS_ALLOWED_ACE_TYPE
284            0, 0, 0, 0, // flags
285            0, 0, 0, 1, // ACE4_READ_DATA
286            0, 0, 0, 6, b'O', b'W', b'N', b'E', b'R', b'@', 0, 0,
287        ];
288        let mut input = Bytes::from_static(wire);
289        let ace = decode_nfsace4(&mut input).expect("RFC 7530 nfsace4 literal must decode");
290        assert_eq!(ace.ace_type, AceType::AccessAllowed);
291        assert_eq!(ace.flags, AceFlags(0));
292        assert_eq!(ace.access_mask, AceMask(AceMask::READ_DATA));
293        assert_eq!(ace.who, "OWNER@");
294        assert!(input.is_empty());
295        assert_eq!(encode_one_ace(&ace), wire);
296    }
297
298    #[test]
299    fn roundtrip_ace_with_padding() {
300        // "AB" is 2 bytes, needs 2 bytes padding to reach 4-byte alignment
301        let ace = make_ace(AceType::AccessDenied, 0x40, 0x20000, "AB");
302        let encoded = encode_one_ace(&ace);
303        // 12 (fixed) + 4 (len) + 4 (padded "AB\0\0") = 20
304        assert_eq!(encoded.len(), 20);
305        let mut bytes = Bytes::from(encoded);
306        let decoded = decode_nfsace4(&mut bytes).unwrap();
307        assert_eq!(decoded, ace);
308    }
309
310    #[test]
311    fn roundtrip_acl_empty() {
312        let acl = Acl { aces: vec![] };
313        let mut buf = Vec::new();
314        encode_acl(&acl, &mut buf);
315        assert_eq!(buf, 0u32.to_be_bytes());
316        let mut bytes = Bytes::from(buf);
317        let decoded = decode_acl(&mut bytes).unwrap();
318        assert_eq!(decoded, acl);
319    }
320
321    #[test]
322    fn roundtrip_acl_multiple() {
323        let acl = Acl {
324            aces: vec![
325                make_ace(
326                    AceType::AccessAllowed,
327                    0,
328                    AceMask::READ_DATA | AceMask::EXECUTE,
329                    "OWNER@",
330                ),
331                make_ace(
332                    AceType::AccessAllowed,
333                    AceFlags::IDENTIFIER_GROUP,
334                    AceMask::READ_DATA,
335                    "GROUP@",
336                ),
337                make_ace(AceType::AccessDenied, 0, AceMask::WRITE_DATA, "EVERYONE@"),
338            ],
339        };
340        let mut buf = Vec::new();
341        encode_acl(&acl, &mut buf);
342        let mut bytes = Bytes::from(buf);
343        let decoded = decode_acl(&mut bytes).unwrap();
344        assert_eq!(decoded, acl);
345        assert_eq!(bytes.remaining(), 0);
346    }
347
348    #[test]
349    fn decode_nfsace4_truncated() {
350        let mut bytes = Bytes::from_static(&[0u8; 8]); // need at least 12
351        assert!(decode_nfsace4(&mut bytes).is_err());
352    }
353
354    #[test]
355    fn decode_acl_count_truncated() {
356        let mut bytes = Bytes::from_static(&[0u8; 2]); // need 4 for count
357        assert!(decode_acl(&mut bytes).is_err());
358    }
359
360    #[test]
361    fn decode_acl_exceeds_max() {
362        let mut buf = Vec::new();
363        buf.extend_from_slice(&((MAX_ACES as u32 + 1).to_be_bytes()));
364        let mut bytes = Bytes::from(buf);
365        assert!(decode_acl(&mut bytes).is_err());
366    }
367
368    #[test]
369    fn skip_acl_empty() {
370        let mut buf = Vec::new();
371        buf.extend_from_slice(&0u32.to_be_bytes()); // count = 0
372        let mut bytes = Bytes::from(buf);
373        skip_acl(&mut bytes).unwrap();
374        assert_eq!(bytes.remaining(), 0);
375    }
376
377    #[test]
378    fn skip_acl_with_entries() {
379        let acl = Acl {
380            aces: vec![
381                make_ace(AceType::AccessAllowed, 0, 0x1F, "OWNER@"),
382                make_ace(AceType::AccessDenied, 0, 0x02, "EVERYONE@"),
383            ],
384        };
385        let mut buf = Vec::new();
386        encode_acl(&acl, &mut buf);
387        // Append a sentinel byte to verify skip_acl stops at the right place
388        buf.push(0xFF);
389        let mut bytes = Bytes::from(buf);
390        skip_acl(&mut bytes).unwrap();
391        assert_eq!(bytes.remaining(), 1);
392        assert_eq!(bytes[0], 0xFF);
393    }
394
395    #[test]
396    fn special_who_strings() {
397        for who in &[
398            "OWNER@",
399            "GROUP@",
400            "EVERYONE@",
401            "INTERACTIVE@",
402            "NETWORK@",
403            "BATCH@",
404            "ANONYMOUS@",
405            "AUTHENTICATED@",
406            "SERVICE@",
407        ] {
408            let ace = make_ace(AceType::AccessAllowed, 0, AceMask::READ_DATA, who);
409            let encoded = encode_one_ace(&ace);
410            let mut bytes = Bytes::from(encoded);
411            let decoded = decode_nfsace4(&mut bytes).unwrap();
412            assert_eq!(decoded.who, *who);
413        }
414    }
415
416    #[test]
417    fn encode_setattr_acl_bitmap() {
418        let acl = Acl {
419            aces: vec![make_ace(AceType::AccessAllowed, 0, 0x1F, "OWNER@")],
420        };
421        let (attrmask, vals) = encode_setattr_acl(&acl);
422        assert_eq!(attrmask, vec![1u32 << 12]);
423        // vals should start with count=1
424        assert_eq!(&vals[..4], &1u32.to_be_bytes());
425    }
426
427    #[test]
428    fn nfsv41_acl_round_trips_flags_inherited_ace_and_word_one_bitmap() {
429        let acl = NfsAcl41 {
430            flags: Acl41Flags(Acl41Flags::AUTO_INHERIT | Acl41Flags::PROTECTED),
431            aces: vec![make_ace(
432                AceType::AccessAllowed,
433                AceFlags::FILE_INHERIT | AceFlags::INHERITED,
434                AceMask::READ_DATA | AceMask::READ_ACL,
435                "EVERYONE@",
436            )],
437        };
438        let (bitmap, values) = encode_setattr_acl41(&acl, attrnum::DACL);
439        assert_eq!(bitmap, vec![0, 1 << 26]);
440        assert_eq!(&values[..4], &acl.flags.0.to_be_bytes());
441
442        let mut response = Vec::new();
443        response.extend_from_slice(&2u32.to_be_bytes());
444        response.extend_from_slice(&0u32.to_be_bytes());
445        response.extend_from_slice(&(1u32 << 26).to_be_bytes());
446        response.extend_from_slice(&(values.len() as u32).to_be_bytes());
447        response.extend_from_slice(&values);
448        let mut bytes = Bytes::from(response);
449        assert_eq!(
450            decode_getattr_acl41(&mut bytes, attrnum::DACL).unwrap(),
451            acl
452        );
453    }
454
455    #[test]
456    fn skip_nfsv41_acl_consumes_flags_and_aces_only() {
457        let acl = NfsAcl41 {
458            flags: Acl41Flags(Acl41Flags::DEFAULTED),
459            aces: vec![make_ace(
460                AceType::SystemAudit,
461                AceFlags::SUCCESSFUL_ACCESS,
462                AceMask::WRITE_DATA,
463                "EVERYONE@",
464            )],
465        };
466        let (_, mut values) = encode_setattr_acl41(&acl, attrnum::SACL);
467        values.push(0xff);
468        let mut bytes = Bytes::from(values);
469        skip_acl41(&mut bytes).unwrap();
470        assert_eq!(bytes.as_ref(), &[0xff]);
471    }
472
473    #[test]
474    fn ace_flags_contains() {
475        let flags = AceFlags(AceFlags::FILE_INHERIT | AceFlags::DIRECTORY_INHERIT);
476        assert!(flags.contains(AceFlags::FILE_INHERIT));
477        assert!(flags.contains(AceFlags::DIRECTORY_INHERIT));
478        assert!(!flags.contains(AceFlags::INHERIT_ONLY));
479    }
480
481    #[test]
482    fn ace_mask_contains() {
483        let mask = AceMask(AceMask::READ_DATA | AceMask::WRITE_DATA);
484        assert!(mask.contains(AceMask::READ_DATA));
485        assert!(mask.contains(AceMask::WRITE_DATA));
486        assert!(!mask.contains(AceMask::EXECUTE));
487    }
488
489    #[test]
490    fn acl_support_supports() {
491        let support = AclSupport(AclSupport::ALLOW | AclSupport::DENY);
492        assert!(support.supports(AclSupport::ALLOW));
493        assert!(support.supports(AclSupport::DENY));
494        assert!(!support.supports(AclSupport::AUDIT));
495    }
496
497    #[test]
498    fn decode_getattr_acl_response() {
499        let acl = Acl {
500            aces: vec![make_ace(
501                AceType::AccessAllowed,
502                0,
503                AceMask::READ_DATA,
504                "OWNER@",
505            )],
506        };
507        // Build a GETATTR response with bit 12 set
508        let mut resp = Vec::new();
509        // bitmap: 1 word, bit 12 set
510        resp.extend_from_slice(&1u32.to_be_bytes()); // bitmap_len = 1
511        resp.extend_from_slice(&(1u32 << 12).to_be_bytes()); // word0 with bit 12
512        // attr_vals
513        let mut acl_data = Vec::new();
514        encode_acl(&acl, &mut acl_data);
515        resp.extend_from_slice(&(acl_data.len() as u32).to_be_bytes());
516        resp.extend_from_slice(&acl_data);
517
518        let mut bytes = Bytes::from(resp);
519        let decoded = decode_getattr_acl(&mut bytes).unwrap();
520        assert_eq!(decoded, acl);
521    }
522
523    #[test]
524    fn decode_getattr_acl_missing_bit() {
525        let mut resp = Vec::new();
526        resp.extend_from_slice(&1u32.to_be_bytes()); // bitmap_len = 1
527        resp.extend_from_slice(&0u32.to_be_bytes()); // no bits set
528        resp.extend_from_slice(&0u32.to_be_bytes()); // attr_vals len = 0
529        let mut bytes = Bytes::from(resp);
530        assert!(decode_getattr_acl(&mut bytes).is_err());
531    }
532
533    #[test]
534    fn decode_getattr_aclsupport_response() {
535        let mut resp = Vec::new();
536        resp.extend_from_slice(&1u32.to_be_bytes()); // bitmap_len = 1
537        resp.extend_from_slice(&(1u32 << 13).to_be_bytes()); // word0 with bit 13
538        let support_val = AclSupport::ALLOW | AclSupport::DENY;
539        resp.extend_from_slice(&4u32.to_be_bytes()); // attr_vals len = 4
540        resp.extend_from_slice(&support_val.to_be_bytes());
541        let mut bytes = Bytes::from(resp);
542        let support = decode_getattr_aclsupport(&mut bytes).unwrap();
543        assert_eq!(support, AclSupport(support_val));
544    }
545}