1use super::xdr::{XdrEncoder, XdrDecoder};
12use super::NfsError;
13use crate::constants;
14use tracing::debug;
15
16pub const OP_SEQUENCE: u32 = 53;
18pub const OP_PUTFH: u32 = 22;
20pub const OP_OPEN: u32 = 18;
22pub const OP_WRITE: u32 = 38;
24pub const OP_READ: u32 = 25;
26pub const OP_SETATTR: u32 = 34;
28pub const OP_CLOSE: u32 = 4;
30pub const OP_GETATTR: u32 = 9;
32pub const OP_EXCHANGE_ID: u32 = 42;
34pub const OP_CREATE_SESSION: u32 = 43;
36pub const OP_DESTROY_SESSION: u32 = 44;
38pub const OP_PUTROOTFH: u32 = 24;
40pub const OP_LOOKUP: u32 = 15;
42pub const OP_GETFH: u32 = 10;
44pub const OP_RECLAIM_COMPLETE: u32 = 58;
46
47pub const NFS4_OK: u32 = 0;
49pub const NFS4ERR_STALE: u32 = 70;
51pub const NFS4ERR_NOENT: u32 = 2;
53pub const NFS4ERR_EXIST: u32 = 17;
55pub const NFS4ERR_ACCESS: u32 = 13;
57pub const NFS4ERR_INVAL: u32 = 22;
59pub const NFS4ERR_NOTDIR: u32 = 20;
61pub const NFS4ERR_ISDIR: u32 = 21;
63pub const NFS4ERR_FBIG: u32 = 27;
65pub const NFS4ERR_NOSPC: u32 = 28;
67pub const NFS4ERR_ROFS: u32 = 30;
69pub const NFS4ERR_NAMETOOLONG: u32 = 63;
71pub const NFS4ERR_NOTEMPTY: u32 = 66;
73pub const NFS4ERR_DELAY: u32 = 10008;
75pub const NFS4ERR_BAD_STATEID: u32 = 10025;
77pub const NFS4ERR_BADSESSION: u32 = 10052;
79pub const NFS4ERR_BADSEQ: u32 = 10026;
81pub const NFS4ERR_SEQ_MISORDERED: u32 = 10063;
83
84pub const RPC_VERSION: u32 = 2;
86pub const NFS_PROGRAM: u32 = 100003;
88pub const NFS_V4: u32 = 4;
90pub const NFSPROC4_COMPOUND: u32 = 1;
92pub const NFSPROC4_NULL: u32 = 0;
94pub const AUTH_SYS: u32 = 1;
96pub const AUTH_NONE: u32 = 0;
98
99pub const OPEN4_SHARE_ACCESS_READ: u32 = 0x00000001;
101pub const OPEN4_SHARE_ACCESS_WRITE: u32 = 0x00000002;
103pub const OPEN4_SHARE_ACCESS_BOTH: u32 = 0x00000003;
105pub const OPEN4_SHARE_DENY_NONE: u32 = 0x00000000;
107pub const CLAIM_NULL: u32 = 0;
109pub const OPEN4_CREATE: u32 = 1;
111pub const OPEN4_NOCREATE: u32 = 0;
113pub const CREATEMODE4_UNCHECKED: u32 = 0;
115
116pub const UNSTABLE4: u32 = 0;
118pub const DATA_SYNC4: u32 = 1;
120pub const FILE_SYNC4: u32 = 2;
122
123pub const FATTR4_SIZE: u32 = 4;
125
126pub const FATTR4_MODE: u32 = 33;
128pub const FATTR4_OWNER: u32 = 36;
130pub const FATTR4_OWNER_GROUP: u32 = 37;
132pub const FATTR4_TIME_MODIFY: u32 = 53;
134pub const FATTR4_TIME_MODIFY_SET: u32 = 52;
136
137pub const MSG_ACCEPTED: u32 = 0;
139pub const SUCCESS: u32 = 0;
141
142#[derive(Debug, Clone)]
152pub struct RpcMessage {
153 pub xid: u32,
155 pub body: Vec<u8>,
157}
158
159pub fn frame_rpc_message(body: &[u8]) -> Vec<u8> {
165 let rm = 0x80000000u32 | (body.len() as u32);
166 let mut msg = Vec::with_capacity(4 + body.len());
167 msg.extend_from_slice(&rm.to_be_bytes());
168 msg.extend_from_slice(body);
169 msg
170}
171
172#[derive(Debug, Clone, Copy)]
178#[derive(Default)]
179pub struct StateId {
180 pub seqid: u32,
181 pub other: [u8; 12],
182}
183
184
185impl StateId {
186 pub fn current() -> Self {
191 Self { seqid: 1, other: [0u8; 12] }
192 }
193}
194
195#[derive(Debug, Clone, Copy)]
197pub enum WriteStable {
198 Unstable,
199 DataSync,
200 FileSync,
201}
202
203#[derive(Debug, Clone)]
205pub enum Nfs4Op {
206 Sequence {
207 session_id: [u8; 16],
208 sequence_id: u32,
209 slot_id: u32,
210 highest_slot_id: u32,
211 cache_this: bool,
212 },
213 PutFh {
214 handle: Vec<u8>,
215 },
216 PutRootFh,
217 Lookup {
218 name: String,
219 },
220 Open {
221 seqid: u32,
222 share_access: u32,
223 share_deny: u32,
224 clientid: u64,
225 owner: Vec<u8>,
226 filename: String,
227 mode: u32,
228 create_owner: Option<String>,
229 create_group: Option<String>,
230 create_mtime: Option<(i64, i64)>,
231 },
232 Write {
233 stateid: StateId,
234 offset: u64,
235 stable: WriteStable,
236 data: Vec<u8>,
237 },
238 Read {
239 stateid: StateId,
240 offset: u64,
241 count: u32,
242 },
243 SetAttr {
244 stateid: StateId,
245 mode: Option<u32>,
246 owner: Option<String>,
247 owner_group: Option<String>,
248 mtime: Option<(i64, i64)>,
249 },
250 Close {
251 seqid: u32,
252 stateid: StateId,
253 },
254 GetAttr {
255 attr_request: [u32; 2],
256 },
257 GetFh,
258 ReclaimComplete,
260}
261
262#[derive(Debug)]
264pub struct OpResult {
265 pub op: u32,
266 pub status: u32,
267 pub stateid: Option<StateId>,
269 pub filehandle: Option<Vec<u8>>,
271 pub size: Option<u64>,
273 pub mtime: Option<(i64, i64)>,
275 pub data: Option<Vec<u8>>,
277}
278
279#[derive(Debug)]
281pub struct CompoundReply {
282 pub xid: u32,
283 pub status: u32,
284 pub tag: Vec<u8>,
285 pub op_results: Vec<OpResult>,
286}
287
288fn encode_auth_sys(enc: &mut XdrEncoder, uid: u32, gid: u32, machine: &str) {
294 enc.encode_u32(AUTH_SYS); let mut body = XdrEncoder::new(64);
297 body.encode_u32(0); body.encode_string(machine); body.encode_u32(uid); body.encode_u32(gid); body.encode_u32(1); body.encode_u32(gid); let body_bytes = body.into_bytes();
304 enc.encode_opaque(&body_bytes);
305}
306
307fn encode_op(enc: &mut XdrEncoder, op: &Nfs4Op) {
309 match op {
310 Nfs4Op::Sequence { session_id, sequence_id, slot_id, highest_slot_id, cache_this } => {
311 enc.encode_u32(OP_SEQUENCE);
312 enc.encode_opaque_fixed(session_id); enc.encode_u32(*sequence_id);
314 enc.encode_u32(*slot_id);
315 enc.encode_u32(*highest_slot_id);
316 enc.encode_bool(*cache_this);
317 }
318 Nfs4Op::PutFh { handle } => {
319 enc.encode_u32(OP_PUTFH);
320 enc.encode_opaque(handle);
321 }
322 Nfs4Op::PutRootFh => {
323 enc.encode_u32(OP_PUTROOTFH);
324 }
325 Nfs4Op::Lookup { name } => {
326 enc.encode_u32(OP_LOOKUP);
327 enc.encode_string(name);
328 }
329 Nfs4Op::Open { seqid, share_access, share_deny, clientid, owner, filename, mode,
330 create_owner, create_group, create_mtime } => {
331 enc.encode_u32(OP_OPEN);
332 enc.encode_u32(*seqid);
333 enc.encode_u32(*share_access);
334 enc.encode_u32(*share_deny);
335 enc.encode_u64(*clientid);
336 enc.encode_opaque(owner);
337 enc.encode_u32(OPEN4_CREATE);
338 enc.encode_u32(CREATEMODE4_UNCHECKED);
339 let mut bitmap_w1: u32 = 1 << (FATTR4_MODE - 32);
341 if create_owner.is_some() { bitmap_w1 |= 1 << (FATTR4_OWNER - 32); }
342 if create_group.is_some() { bitmap_w1 |= 1 << (FATTR4_OWNER_GROUP - 32); }
343 if create_mtime.is_some() { bitmap_w1 |= 1 << (FATTR4_TIME_MODIFY_SET - 32); }
344 enc.encode_u32(2);
345 enc.encode_u32(0);
346 enc.encode_u32(bitmap_w1);
347 let mut attr = XdrEncoder::new(128);
348 attr.encode_u32(*mode & 0o7777);
349 if let Some(o) = create_owner {
350 attr.encode_string(o);
351 }
352 if let Some(g) = create_group {
353 attr.encode_string(g);
354 }
355 if let Some((secs, nsecs)) = create_mtime {
356 attr.encode_u32(1); attr.encode_i64(*secs);
358 attr.encode_u32((*nsecs as u32).min(999_999_999));
359 }
360 let attr_bytes = attr.into_bytes();
361 enc.encode_opaque(&attr_bytes);
362 enc.encode_u32(CLAIM_NULL);
363 enc.encode_string(filename);
364 }
365 Nfs4Op::Write { stateid, offset, stable, data } => {
366 enc.encode_u32(OP_WRITE);
367 enc.encode_u32(stateid.seqid);
369 enc.encode_opaque_fixed(&stateid.other);
370 enc.encode_u64(*offset);
371 enc.encode_u32(match stable {
372 WriteStable::Unstable => UNSTABLE4,
373 WriteStable::DataSync => DATA_SYNC4,
374 WriteStable::FileSync => FILE_SYNC4,
375 });
376 enc.encode_opaque(data);
377 }
378 Nfs4Op::Read { stateid, offset, count } => {
379 enc.encode_u32(OP_READ);
380 enc.encode_u32(stateid.seqid);
381 enc.encode_opaque_fixed(&stateid.other);
382 enc.encode_u64(*offset);
383 enc.encode_u32(*count);
384 }
385 Nfs4Op::SetAttr { stateid, mode, owner, owner_group, mtime } => {
386 enc.encode_u32(OP_SETATTR);
387 enc.encode_u32(stateid.seqid);
388 enc.encode_opaque_fixed(&stateid.other);
389 let mut bitmap_w1: u32 = 0;
390 if mode.is_some() { bitmap_w1 |= 1 << (FATTR4_MODE - 32); }
391 if owner.is_some() { bitmap_w1 |= 1 << (FATTR4_OWNER - 32); }
392 if owner_group.is_some() { bitmap_w1 |= 1 << (FATTR4_OWNER_GROUP - 32); }
393 if mtime.is_some() { bitmap_w1 |= 1 << (FATTR4_TIME_MODIFY_SET - 32); }
394 enc.encode_u32(2); enc.encode_u32(0); enc.encode_u32(bitmap_w1);
397 let mut attr = XdrEncoder::new(128);
398 if let Some(m) = mode {
399 attr.encode_u32(*m);
400 }
401 if let Some(o) = owner {
402 attr.encode_string(o);
403 }
404 if let Some(g) = owner_group {
405 attr.encode_string(g);
406 }
407 if let Some((secs, nsecs)) = mtime {
408 let clamped_nsecs = (*nsecs as u32).min(999_999_999);
409 if *nsecs < 0 || *nsecs > 999_999_999 {
410 debug!(
411 "SETATTR: clamping out-of-range nseconds {} -> {}",
412 nsecs, clamped_nsecs
413 );
414 }
415 attr.encode_u32(1); attr.encode_i64(*secs);
417 attr.encode_u32(clamped_nsecs);
418 }
419 let attr_bytes = attr.into_bytes();
420 debug!(
421 "SETATTR: bitmap_w1={:#010x} attr_vals={} bytes owner={:?} group={:?} mtime={:?}",
422 bitmap_w1, attr_bytes.len(), owner, owner_group, mtime
423 );
424 enc.encode_opaque(&attr_bytes);
425 }
426 Nfs4Op::Close { seqid, stateid } => {
427 enc.encode_u32(OP_CLOSE);
428 enc.encode_u32(*seqid);
429 enc.encode_u32(stateid.seqid);
430 enc.encode_opaque_fixed(&stateid.other);
431 }
432 Nfs4Op::GetAttr { attr_request } => {
433 enc.encode_u32(OP_GETATTR);
434 enc.encode_u32(2); enc.encode_u32(attr_request[0]);
436 enc.encode_u32(attr_request[1]);
437 }
438 Nfs4Op::GetFh => {
439 enc.encode_u32(OP_GETFH);
440 }
441 Nfs4Op::ReclaimComplete => {
442 enc.encode_u32(OP_RECLAIM_COMPLETE);
443 enc.encode_bool(false); }
445 }
446}
447
448pub fn build_compound(
453 xid: u32,
454 tag: &str,
455 uid: u32,
456 gid: u32,
457 machine: &str,
458 ops: &[Nfs4Op],
459) -> Vec<u8> {
460 let mut body = XdrEncoder::new(constants::NFS_XDR_ENCODER_BODY_CAPACITY);
461
462 body.encode_u32(xid); body.encode_u32(0); body.encode_u32(RPC_VERSION); body.encode_u32(NFS_PROGRAM); body.encode_u32(NFS_V4); body.encode_u32(NFSPROC4_COMPOUND); encode_auth_sys(&mut body, uid, gid, machine);
472
473 body.encode_u32(AUTH_NONE);
475 body.encode_u32(0); body.encode_string(tag); body.encode_u32(2); body.encode_u32(ops.len() as u32); for op in ops {
483 encode_op(&mut body, op);
484 }
485
486 body.into_bytes()
487}
488
489pub fn parse_compound_reply(data: &[u8]) -> Result<CompoundReply, NfsError> {
494 if data.len() < 4 {
496 return Err(NfsError::XdrDecode("reply too short".into()));
497 }
498 let mut dec = XdrDecoder::new(&data[4..]);
499
500 let xid = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
502 let msg_type = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
503 if msg_type != 1 { return Err(NfsError::RpcError(format!("expected REPLY (1), got {}", msg_type)));
505 }
506
507 let reply_stat = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
508 if reply_stat != MSG_ACCEPTED {
509 return Err(NfsError::RpcError(format!("RPC rejected: {}", reply_stat)));
510 }
511
512 let _verf_flavor = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
514 let _verf_body = dec.decode_opaque().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
515
516 let accept_stat = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
518 if accept_stat != SUCCESS {
519 return Err(NfsError::RpcError(format!("RPC accept error: {}", accept_stat)));
520 }
521
522 let compound_status = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
524 let tag = dec.decode_opaque().map_err(|e| NfsError::XdrDecode(e.to_string()))?.to_vec();
525 let num_results = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
526
527 let mut op_results = Vec::with_capacity(num_results as usize);
528 for _ in 0..num_results {
529 let op = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
530 let status = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
531
532 let mut filehandle = None;
533 let stateid = None;
534 let mut size = None;
535 let mut mtime = None;
536 let mut data_field: Option<Vec<u8>> = None;
537
538 if status == NFS4_OK {
540 match op {
541 OP_SEQUENCE => {
542 dec.skip_raw(16 + 4 + 4 + 4 + 4 + 4).map_err(|e| NfsError::XdrDecode(e.to_string()))?;
543 }
544 OP_PUTFH | OP_PUTROOTFH | OP_LOOKUP | OP_RECLAIM_COMPLETE => {
545 }
547 OP_GETFH => {
548 let fh = dec.decode_opaque().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
549 filehandle = Some(fh.to_vec());
550 }
551 OP_OPEN => {
552 let sid_seqid = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
554 let sid_other = dec.decode_opaque_fixed(12).map_err(|e| NfsError::XdrDecode(e.to_string()))?;
555 let mut other = [0u8; 12];
556 other.copy_from_slice(sid_other);
557
558 dec.skip_raw(20).map_err(|e| NfsError::XdrDecode(e.to_string()))?;
560 dec.skip_raw(4).map_err(|e| NfsError::XdrDecode(e.to_string()))?;
562 let bm_len = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
564 dec.skip_raw(bm_len as usize * 4).map_err(|e| NfsError::XdrDecode(e.to_string()))?;
565 let deleg_type = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
567 match deleg_type {
568 0 => { }
569 3 => {
570 let why = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
572 if why == 1 || why == 2 {
573 dec.skip_raw(4).map_err(|e| NfsError::XdrDecode(e.to_string()))?;
574 }
575 }
576 _ => {
577 op_results.push(OpResult { op, status, stateid: Some(StateId { seqid: sid_seqid, other }), filehandle: None, size: None, mtime: None, data: None });
579 break;
580 }
581 }
582
583 op_results.push(OpResult { op, status, stateid: Some(StateId { seqid: sid_seqid, other }), filehandle: None, size: None, mtime: None, data: None });
584 continue;
585 }
586 OP_WRITE => {
587 dec.skip_raw(4 + 4 + 8).map_err(|e| NfsError::XdrDecode(e.to_string()))?;
588 }
589 OP_SETATTR => {
590 let bm_len = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
591 dec.skip_raw(bm_len as usize * 4).map_err(|e| NfsError::XdrDecode(e.to_string()))?;
592 }
593 OP_CLOSE => {
594 dec.skip_raw(4 + 12).map_err(|e| NfsError::XdrDecode(e.to_string()))?;
595 }
596 OP_READ => {
597 let _eof = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
598 let raw = dec.decode_opaque().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
599 data_field = Some(raw.to_vec());
600 }
601 OP_GETATTR => {
602 let bm_len = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
604 let mut bitmap = vec![0u32; bm_len as usize];
605 for b in bitmap.iter_mut() {
606 *b = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
607 }
608 let attr_data = dec.decode_opaque().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
609 let mut attr_dec = XdrDecoder::new(attr_data);
611 if bm_len > 0 && (bitmap[0] & (1 << FATTR4_SIZE)) != 0 {
612 size = attr_dec.decode_u64().ok();
613 }
614 if bm_len > 1 && (bitmap[1] & (1 << (FATTR4_TIME_MODIFY - 32))) != 0 {
615 if let (Ok(secs), Ok(nsecs)) = (attr_dec.decode_u64(), attr_dec.decode_u32()) {
617 mtime = Some((secs as i64, nsecs as i64));
618 }
619 }
620 }
621 _ => {
622 break; }
624 }
625 }
626
627 op_results.push(OpResult { op, status, stateid, filehandle, size, mtime, data: data_field });
628 }
629
630 Ok(CompoundReply {
631 xid,
632 status: compound_status,
633 tag,
634 op_results,
635 })
636}
637
638pub fn nfs4_error_name(code: u32) -> &'static str {
640 match code {
641 NFS4_OK => "NFS4_OK",
642 1 => "NFS4ERR_PERM",
643 NFS4ERR_NOENT => "NFS4ERR_NOENT",
644 5 => "NFS4ERR_IO",
645 6 => "NFS4ERR_NXIO",
646 NFS4ERR_ACCESS => "NFS4ERR_ACCESS",
647 NFS4ERR_EXIST => "NFS4ERR_EXIST",
648 18 => "NFS4ERR_XDEV",
649 NFS4ERR_NOTDIR => "NFS4ERR_NOTDIR",
650 NFS4ERR_ISDIR => "NFS4ERR_ISDIR",
651 NFS4ERR_INVAL => "NFS4ERR_INVAL",
652 NFS4ERR_FBIG => "NFS4ERR_FBIG",
653 NFS4ERR_NOSPC => "NFS4ERR_NOSPC",
654 NFS4ERR_ROFS => "NFS4ERR_ROFS",
655 NFS4ERR_NAMETOOLONG => "NFS4ERR_NAMETOOLONG",
656 NFS4ERR_NOTEMPTY => "NFS4ERR_NOTEMPTY",
657 NFS4ERR_STALE => "NFS4ERR_STALE",
658 10003 => "NFS4ERR_BADHANDLE",
659 10006 => "NFS4ERR_SERVERFAULT",
660 10008 => "NFS4ERR_DELAY",
661 10010 => "NFS4ERR_SAME",
662 10011 => "NFS4ERR_DENIED",
663 10013 => "NFS4ERR_GRACE",
664 10015 => "NFS4ERR_SHARE_DENIED",
665 10016 => "NFS4ERR_WRONGSEC",
666 10019 => "NFS4ERR_REQ_TOO_BIG",
667 NFS4ERR_BAD_STATEID => "NFS4ERR_BAD_STATEID",
668 NFS4ERR_BADSEQ => "NFS4ERR_BADSEQ",
669 10028 => "NFS4ERR_REP_TOO_BIG",
670 NFS4ERR_BADSESSION => "NFS4ERR_BADSESSION",
671 10053 => "NFS4ERR_BADSLOT",
672 10054 => "NFS4ERR_COMPLETE_ALREADY",
673 NFS4ERR_SEQ_MISORDERED => "NFS4ERR_SEQ_MISORDERED",
674 10044 => "NFS4ERR_OP_ILLEGAL",
675 10068 => "NFS4ERR_RETRY_UNCACHED_REP",
676 _ => "NFS4ERR_UNKNOWN",
677 }
678}
679
680pub const AUTH_RPCSEC_GSS: u32 = 6;
682pub const AUTH_TLS: u32 = 7;
688pub const RPCSEC_GSS_VERSION: u32 = 1;
690pub const RPCSEC_GSS_DATA: u32 = 0;
692#[cfg(feature = "krb5")]
694pub const RPCSEC_GSS_INIT: u32 = 1;
695#[cfg(feature = "krb5")]
697pub const RPCSEC_GSS_CONTINUE_INIT: u32 = 2;
698
699#[cfg(feature = "krb5")]
705pub fn build_compound_gss(
706 xid: u32,
707 tag: &str,
708 ops: &[Nfs4Op],
709 gss: &mut super::gss::GssContext,
710) -> Result<Vec<u8>, super::NfsError> {
711 use super::gss::RpcGssService;
712
713 let service = gss.rpc_gss_service();
714
715 gss.sequence_num += 1;
717
718 let mut args_enc = XdrEncoder::new(constants::NFS_XDR_ENCODER_BODY_CAPACITY);
720 args_enc.encode_string(tag);
721 args_enc.encode_u32(2); args_enc.encode_u32(ops.len() as u32);
723 for op in ops {
724 encode_op(&mut args_enc, op);
725 }
726 let compound_args = args_enc.into_bytes();
727
728 debug!(
729 "build_compound_gss: tag={} ops={} compound_args={} bytes, last 32: {:02x?}",
730 tag, ops.len(), compound_args.len(),
731 &compound_args[compound_args.len().saturating_sub(32)..]
732 );
733
734 let mut header = XdrEncoder::new(constants::NFS_XDR_ENCODER_TINY_CAPACITY);
736 header.encode_u32(xid);
737 header.encode_u32(0); header.encode_u32(RPC_VERSION);
739 header.encode_u32(NFS_PROGRAM);
740 header.encode_u32(NFS_V4);
741 header.encode_u32(NFSPROC4_COMPOUND);
742
743 let mut cred_body = XdrEncoder::new(64);
745 cred_body.encode_u32(RPCSEC_GSS_VERSION);
746 cred_body.encode_u32(RPCSEC_GSS_DATA);
747 cred_body.encode_u32(gss.sequence_num);
748 cred_body.encode_u32(service as u32);
749 cred_body.encode_opaque(&gss.server_handle);
750 let cred_bytes = cred_body.into_bytes();
751
752 header.encode_u32(AUTH_RPCSEC_GSS);
753 header.encode_opaque(&cred_bytes);
754
755 let header_bytes = header.into_bytes();
756
757 let mic = gss.get_mic(&header_bytes)?;
759
760 let mut body = XdrEncoder::new(header_bytes.len() + mic.len() + compound_args.len() + 64);
762 body.encode_raw(&header_bytes);
763
764 body.encode_u32(AUTH_RPCSEC_GSS);
766 body.encode_opaque(&mic);
767
768 match service {
770 RpcGssService::None => {
771 body.encode_raw(&compound_args);
773 }
774 RpcGssService::Integrity => {
775 let mut integ_data = XdrEncoder::new(compound_args.len() + 8);
777 integ_data.encode_u32(gss.sequence_num);
778 integ_data.encode_raw(&compound_args);
779 let integ_bytes = integ_data.into_bytes();
780
781 let integ_mic = gss.get_mic(&integ_bytes)?;
782 body.encode_opaque(&integ_bytes);
783 body.encode_opaque(&integ_mic);
784 }
785 RpcGssService::Privacy => {
786 let mut priv_data = XdrEncoder::new(compound_args.len() + 8);
788 priv_data.encode_u32(gss.sequence_num);
789 priv_data.encode_raw(&compound_args);
790 let priv_bytes = priv_data.into_bytes();
791
792 let wrapped = gss.wrap_body(&priv_bytes)?;
793 debug!(
794 "krb5p wrap: priv_bytes={} wrapped={} overhead={} token_hdr={:02x?}",
795 priv_bytes.len(), wrapped.len(),
796 wrapped.len().saturating_sub(priv_bytes.len()),
797 &wrapped[..wrapped.len().min(16)]
798 );
799 body.encode_opaque(&wrapped);
800 }
801 }
802
803 Ok(body.into_bytes())
804}
805
806#[cfg(feature = "krb5")]
813pub fn build_rpcsec_gss_init(
814 xid: u32,
815 handle: &[u8],
816 gss_token: &[u8],
817) -> Vec<u8> {
818 let mut body = XdrEncoder::new(256 + gss_token.len());
819
820 body.encode_u32(xid);
822 body.encode_u32(0); body.encode_u32(RPC_VERSION);
824 body.encode_u32(NFS_PROGRAM);
825 body.encode_u32(NFS_V4);
826 body.encode_u32(NFSPROC4_NULL); let proc = if handle.is_empty() { RPCSEC_GSS_INIT } else { RPCSEC_GSS_CONTINUE_INIT };
830 let mut cred_body = XdrEncoder::new(64);
831 cred_body.encode_u32(RPCSEC_GSS_VERSION); cred_body.encode_u32(proc); cred_body.encode_u32(0); cred_body.encode_u32(1); cred_body.encode_opaque(handle); let cred_bytes = cred_body.into_bytes();
837
838 body.encode_u32(AUTH_RPCSEC_GSS); body.encode_opaque(&cred_bytes); body.encode_u32(AUTH_NONE);
843 body.encode_u32(0);
844
845 body.encode_opaque(gss_token);
847
848 body.into_bytes()
849}
850
851#[cfg(feature = "krb5")]
855pub fn parse_rpcsec_gss_init_reply(
856 data: &[u8],
857) -> Result<(Vec<u8>, u32, u32, u32, Vec<u8>), NfsError> {
858 if data.len() < 4 {
859 return Err(NfsError::XdrDecode("RPCSEC_GSS INIT reply too short".into()));
860 }
861 let mut dec = XdrDecoder::new(&data[4..]); let _xid = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
865 let msg_type = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
866 if msg_type != 1 {
867 return Err(NfsError::RpcError(format!("expected REPLY (1), got {}", msg_type)));
868 }
869
870 let reply_stat = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
871 if reply_stat != MSG_ACCEPTED {
872 return Err(NfsError::RpcError(format!("RPCSEC_GSS INIT rejected: {}", reply_stat)));
873 }
874
875 let _verf_flavor = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
877 let _verf_body = dec.decode_opaque().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
878
879 let accept_stat = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
881 if accept_stat != SUCCESS {
882 return Err(NfsError::RpcError(format!("RPCSEC_GSS INIT accept error: {}", accept_stat)));
883 }
884
885 let server_handle = dec.decode_opaque().map_err(|e| NfsError::XdrDecode(e.to_string()))?.to_vec();
887 let gss_major = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
888 let gss_minor = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
889 let seq_window = dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
890 let cont_token = dec.decode_opaque().map_err(|e| NfsError::XdrDecode(e.to_string()))?.to_vec();
891
892 Ok((server_handle, gss_major, gss_minor, seq_window, cont_token))
893}
894
895#[cfg(feature = "krb5")]
901pub fn unwrap_gss_reply(
902 data: &[u8],
903 gss: &mut super::gss::GssContext,
904) -> Result<Vec<u8>, NfsError> {
905 use super::gss::RpcGssService;
906
907 let service = gss.rpc_gss_service();
908 if matches!(service, RpcGssService::None) {
909 return Ok(data.to_vec());
910 }
911
912 if data.len() < 4 {
913 return Err(NfsError::XdrDecode("GSS reply too short".into()));
914 }
915 let mut dec = XdrDecoder::new(&data[4..]); dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?; dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?; dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?; dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?; dec.decode_opaque().map_err(|e| NfsError::XdrDecode(e.to_string()))?; dec.decode_u32().map_err(|e| NfsError::XdrDecode(e.to_string()))?; let header_end = 4 + dec.position(); let compound_body = match service {
928 RpcGssService::None => unreachable!(),
929 RpcGssService::Privacy => {
930 let wrapped = dec.decode_opaque().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
931 let plaintext = gss.unwrap_body(wrapped)?;
932 if plaintext.len() < 4 {
933 return Err(NfsError::XdrDecode("unwrapped krb5p reply too short for seq_num".into()));
934 }
935 plaintext[4..].to_vec() }
937 RpcGssService::Integrity => {
938 let integ_data = dec.decode_opaque().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
939 let checksum = dec.decode_opaque().map_err(|e| NfsError::XdrDecode(e.to_string()))?;
940 gss.verify_mic(integ_data, checksum)?;
941 if integ_data.len() < 4 {
942 return Err(NfsError::XdrDecode("krb5i integrity data too short for seq_num".into()));
943 }
944 integ_data[4..].to_vec() }
946 };
947
948 debug!(
949 "unwrap_gss_reply: header_end={} compound_body={} bytes, hex: {:02x?}",
950 header_end, compound_body.len(), &compound_body[..compound_body.len().min(200)]
951 );
952
953 let total_len = header_end + compound_body.len();
955 let mut result = Vec::with_capacity(total_len);
956 result.extend_from_slice(&data[..header_end]);
957 result.extend_from_slice(&compound_body);
958 let rm = 0x80000000u32 | ((total_len - 4) as u32);
960 result[..4].copy_from_slice(&rm.to_be_bytes());
961
962 Ok(result)
963}
964
965pub fn build_null_call(xid: u32) -> Vec<u8> {
970 let mut body = XdrEncoder::new(64);
971 body.encode_u32(xid);
972 body.encode_u32(0); body.encode_u32(RPC_VERSION);
974 body.encode_u32(NFS_PROGRAM);
975 body.encode_u32(NFS_V4);
976 body.encode_u32(NFSPROC4_NULL); body.encode_u32(AUTH_NONE);
979 body.encode_u32(0);
980 body.encode_u32(AUTH_NONE);
981 body.encode_u32(0);
982 body.into_bytes()
983}
984
985pub fn build_auth_tls_probe(xid: u32) -> Vec<u8> {
992 let mut body = XdrEncoder::new(64);
993 body.encode_u32(xid);
994 body.encode_u32(0); body.encode_u32(RPC_VERSION);
996 body.encode_u32(NFS_PROGRAM);
997 body.encode_u32(NFS_V4);
998 body.encode_u32(NFSPROC4_NULL);
999 body.encode_u32(AUTH_TLS);
1001 body.encode_u32(0);
1002 body.encode_u32(AUTH_NONE);
1004 body.encode_u32(0);
1005 body.into_bytes()
1006}
1007
1008pub fn parse_auth_tls_reply(reply: &[u8]) -> bool {
1013 if reply.len() < 32 {
1016 return false;
1017 }
1018 let mut dec = XdrDecoder::new(&reply[4..]); let Ok(_xid) = dec.decode_u32() else { return false };
1020 let Ok(msg_type) = dec.decode_u32() else { return false };
1021 if msg_type != 1 {
1022 return false;
1023 }
1024 let Ok(accept_status) = dec.decode_u32() else { return false };
1025 if accept_status != MSG_ACCEPTED {
1026 return false;
1027 }
1028 let Ok(verf_flavor) = dec.decode_u32() else { return false };
1029 let Ok(verf_len) = dec.decode_u32() else { return false };
1030 if verf_flavor != AUTH_NONE || verf_len != 8 {
1031 return false;
1032 }
1033 if dec.remaining() < 8 {
1034 return false;
1035 }
1036 let Ok(verf_body) = dec.decode_opaque_fixed(8) else { return false };
1037 verf_body == b"STARTTLS"
1038}
1039
1040#[cfg(test)]
1041mod tests {
1042 #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
1043 use super::*;
1044
1045 #[test]
1046 fn auth_tls_probe_xdr_encoding() {
1047 let probe = build_auth_tls_probe(0x42);
1048 assert_eq!(probe.len(), 40);
1050 assert_eq!(u32::from_be_bytes(probe[0..4].try_into().unwrap()), 0x42); assert_eq!(u32::from_be_bytes(probe[4..8].try_into().unwrap()), 0); assert_eq!(u32::from_be_bytes(probe[8..12].try_into().unwrap()), 2); assert_eq!(u32::from_be_bytes(probe[12..16].try_into().unwrap()), 100003); assert_eq!(u32::from_be_bytes(probe[16..20].try_into().unwrap()), 4); assert_eq!(u32::from_be_bytes(probe[20..24].try_into().unwrap()), 0); assert_eq!(u32::from_be_bytes(probe[24..28].try_into().unwrap()), 7); assert_eq!(u32::from_be_bytes(probe[28..32].try_into().unwrap()), 0); assert_eq!(u32::from_be_bytes(probe[32..36].try_into().unwrap()), 0); assert_eq!(u32::from_be_bytes(probe[36..40].try_into().unwrap()), 0); }
1061
1062 #[test]
1063 fn auth_tls_probe_differs_from_null_only_in_credential_flavor() {
1064 let null_call = build_null_call(0xAA);
1065 let tls_probe = build_auth_tls_probe(0xAA);
1066 assert_eq!(null_call.len(), tls_probe.len());
1067 assert_eq!(&null_call[..24], &tls_probe[..24]);
1069 assert_eq!(u32::from_be_bytes(null_call[24..28].try_into().unwrap()), 0);
1071 assert_eq!(u32::from_be_bytes(tls_probe[24..28].try_into().unwrap()), 7);
1072 assert_eq!(&null_call[28..], &tls_probe[28..]);
1074 }
1075
1076 #[test]
1077 fn frame_rpc_message_prepends_record_mark() {
1078 let body = build_null_call(0x42);
1079 let framed = frame_rpc_message(&body);
1080 assert_eq!(framed.len(), 4 + body.len());
1081 let rm = u32::from_be_bytes(framed[0..4].try_into().unwrap());
1082 assert_eq!(rm, 0x80000000 | (body.len() as u32));
1083 assert_eq!(&framed[4..], &body);
1084 }
1085
1086 #[test]
1087 fn parse_auth_tls_reply_detects_starttls() {
1088 let mut reply = Vec::new();
1089 reply.extend_from_slice(&(0x80000000u32 | 32).to_be_bytes()); reply.extend_from_slice(&0xAAu32.to_be_bytes()); reply.extend_from_slice(&1u32.to_be_bytes()); reply.extend_from_slice(&0u32.to_be_bytes()); reply.extend_from_slice(&0u32.to_be_bytes()); reply.extend_from_slice(&8u32.to_be_bytes()); reply.extend_from_slice(b"STARTTLS"); reply.extend_from_slice(&0u32.to_be_bytes()); assert!(parse_auth_tls_reply(&reply));
1098 }
1099
1100 #[test]
1101 fn parse_auth_tls_reply_rejects_no_tls_support() {
1102 let mut reply = Vec::new();
1104 reply.extend_from_slice(&(0x80000000u32 | 24).to_be_bytes());
1105 reply.extend_from_slice(&0xBBu32.to_be_bytes());
1106 reply.extend_from_slice(&1u32.to_be_bytes());
1107 reply.extend_from_slice(&0u32.to_be_bytes());
1108 reply.extend_from_slice(&0u32.to_be_bytes()); reply.extend_from_slice(&0u32.to_be_bytes()); reply.extend_from_slice(&0u32.to_be_bytes()); assert!(!parse_auth_tls_reply(&reply));
1112 }
1113
1114 #[test]
1115 fn parse_auth_tls_reply_rejects_truncated_input() {
1116 assert!(!parse_auth_tls_reply(&[]));
1117 assert!(!parse_auth_tls_reply(&[0u8; 16]));
1118 assert!(!parse_auth_tls_reply(&[0u8; 31]));
1119 }
1120
1121 #[test]
1122 fn test_read_op_encode() {
1123 let stateid = StateId { seqid: 0, other: [0u8; 12] };
1124 let op = Nfs4Op::Read { stateid, offset: 0, count: 8192 };
1125 let mut enc = XdrEncoder::new(64);
1126 encode_op(&mut enc, &op);
1127 let bytes = enc.into_bytes();
1128 assert_eq!(bytes.len(), 32, "READ op XDR should be 32 bytes");
1130 assert_eq!(&bytes[0..4], &25u32.to_be_bytes(), "First 4 bytes should be OP_READ (25)");
1132 assert_eq!(&bytes[20..28], &0u64.to_be_bytes(), "Offset should be 0");
1134 assert_eq!(&bytes[28..32], &8192u32.to_be_bytes(), "Last 4 bytes should be count (8192)");
1136 }
1137
1138 #[test]
1139 fn test_read_op_result_data_field() {
1140 let result = OpResult {
1141 op: OP_READ,
1142 status: 0,
1143 stateid: None,
1144 filehandle: None,
1145 size: None,
1146 mtime: None,
1147 data: Some(vec![1u8, 2, 3, 4]),
1148 };
1149 assert_eq!(result.data, Some(vec![1u8, 2, 3, 4]));
1150 assert_eq!(result.op, OP_READ);
1151 assert_eq!(result.status, 0);
1152 }
1153}