feat: full NFSv4.2 server and client in pure Go
Test / test (push) Successful in 2m4s
Release / gates (push) Successful in 2m5s
Release / build (amd64, freebsd) (push) Successful in 1m27s
Release / build (amd64, linux) (push) Successful in 1m22s
Release / build (amd64, netbsd) (push) Successful in 1m19s
Release / build (amd64, openbsd) (push) Successful in 1m20s
Release / build (arm64, darwin) (push) Successful in 1m21s
Release / build (arm64, freebsd) (push) Successful in 1m26s
Release / build (arm64, linux) (push) Successful in 1m25s
Release / build (arm64, netbsd) (push) Successful in 1m31s
Release / build (arm64, openbsd) (push) Successful in 1m27s
Release / build (loong64, linux) (push) Successful in 1m37s
Release / build (riscv64, linux) (push) Successful in 1m21s
Release / release (push) Successful in 40s
Test / test (push) Successful in 2m4s
Release / gates (push) Successful in 2m5s
Release / build (amd64, freebsd) (push) Successful in 1m27s
Release / build (amd64, linux) (push) Successful in 1m22s
Release / build (amd64, netbsd) (push) Successful in 1m19s
Release / build (amd64, openbsd) (push) Successful in 1m20s
Release / build (arm64, darwin) (push) Successful in 1m21s
Release / build (arm64, freebsd) (push) Successful in 1m26s
Release / build (arm64, linux) (push) Successful in 1m25s
Release / build (arm64, netbsd) (push) Successful in 1m31s
Release / build (arm64, openbsd) (push) Successful in 1m27s
Release / build (loong64, linux) (push) Successful in 1m37s
Release / build (riscv64, linux) (push) Successful in 1m21s
Release / release (push) Successful in 40s
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// The RPCSEC_GSSv3 structures of RFC 7861: the version three credential,
|
||||
// the CREATE and LIST control procedures and the assertion payloads.
|
||||
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
|
||||
)
|
||||
|
||||
// Control procedure numbers of the rpc_gss_proc_t enumeration, RFC 7861
|
||||
// section 5.1.
|
||||
const (
|
||||
GSSProcCreate = 5
|
||||
GSSProcList = 6
|
||||
)
|
||||
|
||||
// The credential version that carries the new control procedures.
|
||||
const GSSVersion3 = 3
|
||||
|
||||
// Assertion types of the rgss3_assertion_type enumeration.
|
||||
const (
|
||||
AssertionLabel = 0
|
||||
AssertionPrivs = 1
|
||||
)
|
||||
|
||||
// ErrGSSv3 marks a malformed version three control message.
|
||||
var ErrGSSv3 = errors.New("rpc: malformed rpcsec gssv3 message")
|
||||
|
||||
// A GSSv3Cred is the version three credential: the version field rides
|
||||
// in front of the version one shape, RFC 7861 section 5.1.
|
||||
type GSSv3Cred struct {
|
||||
Proc uint32
|
||||
Seq uint32
|
||||
Service uint32
|
||||
Handle []byte
|
||||
}
|
||||
|
||||
// AppendGSSv3Cred encodes the version three credential body.
|
||||
func AppendGSSv3Cred(b []byte, proc, seq, service uint32, handle []byte) []byte {
|
||||
b = xdr.AppendUint32(b, GSSVersion3)
|
||||
b = xdr.AppendUint32(b, proc)
|
||||
b = xdr.AppendUint32(b, seq)
|
||||
b = xdr.AppendUint32(b, service)
|
||||
return xdr.AppendVarOpaque(b, handle)
|
||||
}
|
||||
|
||||
// DecodeGSSv3Cred decodes the version three credential body: the
|
||||
// leading version field is checked and skipped before the credential
|
||||
// proper.
|
||||
func DecodeGSSv3Cred(body []byte) (GSSv3Cred, error) {
|
||||
d := xdr.NewDecoder(body)
|
||||
var c GSSv3Cred
|
||||
var err error
|
||||
if c.Proc, err = d.Uint32(); err != nil {
|
||||
return c, err
|
||||
}
|
||||
if c.Proc != GSSVersion3 {
|
||||
return c, ErrGSSv3
|
||||
}
|
||||
if c.Proc, err = d.Uint32(); err != nil {
|
||||
return c, err
|
||||
}
|
||||
if c.Seq, err = d.Uint32(); err != nil {
|
||||
return c, err
|
||||
}
|
||||
if c.Service, err = d.Uint32(); err != nil {
|
||||
return c, err
|
||||
}
|
||||
c.Handle, err = d.VarOpaque()
|
||||
return c, err
|
||||
}
|
||||
|
||||
// A Label is the rgss3_label assertion: the label format specifier and
|
||||
// the opaque label payload.
|
||||
type Label struct {
|
||||
LfsId uint32
|
||||
PiId uint32
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
// Privs is the rgss3_privs structured privilege: who grants what.
|
||||
type Privs struct {
|
||||
Who string
|
||||
Grant string
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
// An Assertion is one rgss3_assertion_u union member.
|
||||
type Assertion struct {
|
||||
Type uint32
|
||||
Label Label
|
||||
Privs Privs
|
||||
Ext []byte
|
||||
}
|
||||
|
||||
// appendLabel and appendPrivs encode the assertion payloads.
|
||||
func appendLabel(b []byte, l Label) []byte {
|
||||
b = xdr.AppendUint32(b, l.LfsId)
|
||||
b = xdr.AppendUint32(b, l.PiId)
|
||||
return xdr.AppendVarOpaque(b, l.Bytes)
|
||||
}
|
||||
|
||||
func appendPrivs(b []byte, p Privs) []byte {
|
||||
b = xdr.AppendString(b, p.Who)
|
||||
b = xdr.AppendString(b, p.Grant)
|
||||
return xdr.AppendVarOpaque(b, p.Bytes)
|
||||
}
|
||||
|
||||
// AppendAssertion encodes one rgss3_assertion_u union.
|
||||
func AppendAssertion(b []byte, a Assertion) []byte {
|
||||
b = xdr.AppendUint32(b, a.Type)
|
||||
switch a.Type {
|
||||
case AssertionLabel:
|
||||
return appendLabel(b, a.Label)
|
||||
case AssertionPrivs:
|
||||
return appendPrivs(b, a.Privs)
|
||||
default:
|
||||
return xdr.AppendVarOpaque(b, a.Ext)
|
||||
}
|
||||
}
|
||||
|
||||
// DecodeAssertion decodes one rgss3_assertion_u union.
|
||||
func DecodeAssertion(d *xdr.Decoder) (Assertion, error) {
|
||||
var a Assertion
|
||||
var err error
|
||||
if a.Type, err = d.Uint32(); err != nil {
|
||||
return a, err
|
||||
}
|
||||
switch a.Type {
|
||||
case AssertionLabel:
|
||||
if a.Label.LfsId, err = d.Uint32(); err != nil {
|
||||
return a, err
|
||||
}
|
||||
if a.Label.PiId, err = d.Uint32(); err != nil {
|
||||
return a, err
|
||||
}
|
||||
a.Label.Bytes, err = d.VarOpaque()
|
||||
return a, err
|
||||
case AssertionPrivs:
|
||||
if a.Privs.Who, err = d.String(); err != nil {
|
||||
return a, err
|
||||
}
|
||||
if a.Privs.Grant, err = d.String(); err != nil {
|
||||
return a, err
|
||||
}
|
||||
a.Privs.Bytes, err = d.VarOpaque()
|
||||
return a, err
|
||||
default:
|
||||
a.Ext, err = d.VarOpaque()
|
||||
return a, err
|
||||
}
|
||||
}
|
||||
|
||||
// A MpAuth is the rgss3_gss_mp_auth multi-principal authentication
|
||||
// payload: the inner context handle and a MIC of the RPC header made
|
||||
// under the inner context.
|
||||
type MpAuth struct {
|
||||
InnerHandle []byte
|
||||
HeaderMic []byte
|
||||
}
|
||||
|
||||
// appendOptional encodes an XDR optional: the presence flag and the
|
||||
// payload.
|
||||
func appendOptional(b []byte, present bool, enc func([]byte) []byte) []byte {
|
||||
b = xdr.AppendBool(b, present)
|
||||
if present {
|
||||
return enc(b)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// AppendCreateArgs encodes the rgss3_create_args call data.
|
||||
func AppendCreateArgs(b []byte, mpAuth *MpAuth, chanBinding []byte, assertions []Assertion) []byte {
|
||||
b = appendOptional(b, mpAuth != nil, func(x []byte) []byte {
|
||||
x = xdr.AppendVarOpaque(x, mpAuth.InnerHandle)
|
||||
return xdr.AppendVarOpaque(x, mpAuth.HeaderMic)
|
||||
})
|
||||
b = appendOptional(b, chanBinding != nil, func(x []byte) []byte {
|
||||
return xdr.AppendVarOpaque(x, chanBinding)
|
||||
})
|
||||
b = xdr.AppendUint32(b, uint32(len(assertions)))
|
||||
for _, a := range assertions {
|
||||
b = AppendAssertion(b, a)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// DecodeCreateArgs decodes the rgss3_create_args call data.
|
||||
func DecodeCreateArgs(payload []byte) (mpAuth *MpAuth, chanBinding []byte, assertions []Assertion, err error) {
|
||||
d := xdr.NewDecoder(payload)
|
||||
var present bool
|
||||
if present, err = d.Bool(); err != nil {
|
||||
return
|
||||
}
|
||||
if present {
|
||||
mpAuth = &MpAuth{}
|
||||
if mpAuth.InnerHandle, err = d.VarOpaque(); err != nil {
|
||||
return
|
||||
}
|
||||
if mpAuth.HeaderMic, err = d.VarOpaque(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if present, err = d.Bool(); err != nil {
|
||||
return
|
||||
}
|
||||
if present {
|
||||
if chanBinding, err = d.VarOpaque(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
var n uint32
|
||||
if n, err = d.Uint32(); err != nil {
|
||||
return
|
||||
}
|
||||
for i := uint32(0); i < n; i++ {
|
||||
var a Assertion
|
||||
if a, err = DecodeAssertion(d); err != nil {
|
||||
return
|
||||
}
|
||||
assertions = append(assertions, a)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AppendCreateRes encodes the rgss3_create_res reply: the child handle,
|
||||
// the mirrored optional fields and the granted assertions in order.
|
||||
func AppendCreateRes(b []byte, handle []byte, mpAuth *MpAuth, chanBinding []byte, assertions []Assertion) []byte {
|
||||
b = xdr.AppendVarOpaque(b, handle)
|
||||
b = appendOptional(b, mpAuth != nil, func(x []byte) []byte {
|
||||
x = xdr.AppendVarOpaque(x, mpAuth.InnerHandle)
|
||||
return xdr.AppendVarOpaque(x, mpAuth.HeaderMic)
|
||||
})
|
||||
b = appendOptional(b, chanBinding != nil, func(x []byte) []byte {
|
||||
return xdr.AppendVarOpaque(x, chanBinding)
|
||||
})
|
||||
b = xdr.AppendUint32(b, uint32(len(assertions)))
|
||||
for _, a := range assertions {
|
||||
b = AppendAssertion(b, a)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// DecodeCreateRes decodes the rgss3_create_res reply.
|
||||
func DecodeCreateRes(payload []byte) (handle []byte, mpAuth *MpAuth, chanBinding []byte, assertions []Assertion, err error) {
|
||||
d := xdr.NewDecoder(payload)
|
||||
if handle, err = d.VarOpaque(); err != nil {
|
||||
return
|
||||
}
|
||||
var present bool
|
||||
if present, err = d.Bool(); err != nil {
|
||||
return
|
||||
}
|
||||
if present {
|
||||
mpAuth = &MpAuth{}
|
||||
if mpAuth.InnerHandle, err = d.VarOpaque(); err != nil {
|
||||
return
|
||||
}
|
||||
if mpAuth.HeaderMic, err = d.VarOpaque(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if present, err = d.Bool(); err != nil {
|
||||
return
|
||||
}
|
||||
if present {
|
||||
if chanBinding, err = d.VarOpaque(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
var n uint32
|
||||
if n, err = d.Uint32(); err != nil {
|
||||
return
|
||||
}
|
||||
for i := uint32(0); i < n; i++ {
|
||||
var a Assertion
|
||||
if a, err = DecodeAssertion(d); err != nil {
|
||||
return
|
||||
}
|
||||
assertions = append(assertions, a)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AppendListRes encodes the RPCSEC_GSS_LIST reply: the supported
|
||||
// assertion types, RFC 7861 section 5.3.
|
||||
func AppendListRes(b []byte, types []uint32) []byte {
|
||||
b = xdr.AppendUint32(b, uint32(len(types)))
|
||||
for _, t := range types {
|
||||
b = xdr.AppendUint32(b, t)
|
||||
}
|
||||
return b
|
||||
}
|
||||
Reference in New Issue
Block a user