9p: uname-based self-prompt rejection + ollie-9p-mount
Store Uname from Tattach on each connection. Reject writes to
s/{id}/prompt when the connection's uname matches the target session
(self-prompt). Cross-agent prompting remains allowed.
Add ollie-9p-mount: a FUSE-to-9P proxy that attaches with a custom
uname derived from $OLLIE_SESSION_ID. Each session gets a transparent
mount — tools use ordinary file I/O and the identity is carried by the
underlying 9P connection.
Also adds a group registry (AddGroup/RemoveGroup/InGroup) on the server
for future access control use cases.
This commit is contained in:
parent
c1f02cdee1
commit
f9c9bc8969
|
|
@ -0,0 +1,97 @@
|
||||||
|
// ollie-9p-mount — per-session FUSE proxy that attaches to olliesrv with a custom uname.
|
||||||
|
//
|
||||||
|
// Each agent session gets its own mount. Tools running inside the session see
|
||||||
|
// the full ollie namespace through $OLLIE, but all 9P operations carry the
|
||||||
|
// session's identity (Uname). The server can then enforce access control
|
||||||
|
// (e.g. rejecting self-prompts).
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
//
|
||||||
|
// ollie-9p-mount [-u uname] [-s service] [-addr address] <mountpoint>
|
||||||
|
//
|
||||||
|
// If -u is not given, $OLLIE_SESSION_ID is used.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"9fans.net/go/plan9/client"
|
||||||
|
"github.com/hanwen/go-fuse/v2/fs"
|
||||||
|
"github.com/hanwen/go-fuse/v2/fuse"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
userFlag = flag.String("u", "", "uname for 9P attach (default: $OLLIE_SESSION_ID)")
|
||||||
|
serviceFlag = flag.String("s", "ollie", "9P service name")
|
||||||
|
netFlag = flag.String("net", "", "network type (unix, tcp); empty = namespace")
|
||||||
|
addrFlag = flag.String("addr", "", "9P address; empty = namespace service")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
if flag.NArg() != 1 {
|
||||||
|
fmt.Fprintf(os.Stderr, "usage: ollie-9p-mount [-u uname] [-s service] <mountpoint>\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
mnt := flag.Arg(0)
|
||||||
|
|
||||||
|
uname := *userFlag
|
||||||
|
if uname == "" {
|
||||||
|
uname = os.Getenv("OLLIE_SESSION_ID")
|
||||||
|
}
|
||||||
|
if uname == "" {
|
||||||
|
fmt.Fprintf(os.Stderr, "ollie-9p-mount: no uname (-u or $OLLIE_SESSION_ID)\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fsys, err := dial(uname)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("dial: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.MkdirAll(mnt, 0755)
|
||||||
|
root := &p9Dir{fsys: fsys, path: ""}
|
||||||
|
server, err := fs.Mount(mnt, root, &fs.Options{
|
||||||
|
MountOptions: fuse.MountOptions{
|
||||||
|
FsName: "ollie-9p",
|
||||||
|
Name: "ollie",
|
||||||
|
DisableXAttrs: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("mount: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmount on signal.
|
||||||
|
sig := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
go func() {
|
||||||
|
<-sig
|
||||||
|
server.Unmount()
|
||||||
|
}()
|
||||||
|
|
||||||
|
server.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func dial(uname string) (*client.Fsys, error) {
|
||||||
|
var conn *client.Conn
|
||||||
|
var err error
|
||||||
|
if *addrFlag != "" {
|
||||||
|
n := *netFlag
|
||||||
|
if n == "" {
|
||||||
|
n = "tcp"
|
||||||
|
}
|
||||||
|
conn, err = client.Dial(n, *addrFlag)
|
||||||
|
} else {
|
||||||
|
conn, err = client.DialService(*serviceFlag)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn.Attach(nil, uname, "")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"9fans.net/go/plan9"
|
||||||
|
"9fans.net/go/plan9/client"
|
||||||
|
"github.com/hanwen/go-fuse/v2/fs"
|
||||||
|
"github.com/hanwen/go-fuse/v2/fuse"
|
||||||
|
)
|
||||||
|
|
||||||
|
// p9Dir is a FUSE directory node backed by a 9P path.
|
||||||
|
type p9Dir struct {
|
||||||
|
fs.Inode
|
||||||
|
fsys *client.Fsys
|
||||||
|
path string // 9P path (empty string = root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// p9File is a FUSE file node backed by a 9P path.
|
||||||
|
type p9File struct {
|
||||||
|
fs.Inode
|
||||||
|
fsys *client.Fsys
|
||||||
|
path string
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = (fs.NodeGetattrer)((*p9Dir)(nil))
|
||||||
|
var _ = (fs.NodeReaddirer)((*p9Dir)(nil))
|
||||||
|
var _ = (fs.NodeLookuper)((*p9Dir)(nil))
|
||||||
|
|
||||||
|
var _ = (fs.NodeGetattrer)((*p9File)(nil))
|
||||||
|
var _ = (fs.NodeOpener)((*p9File)(nil))
|
||||||
|
var _ = (fs.NodeReader)((*p9File)(nil))
|
||||||
|
var _ = (fs.NodeWriter)((*p9File)(nil))
|
||||||
|
var _ = (fs.NodeSetattrer)((*p9File)(nil))
|
||||||
|
|
||||||
|
func p9path(base, name string) string {
|
||||||
|
if base == "" {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return base + "/" + name
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- p9Dir ---
|
||||||
|
|
||||||
|
func (d *p9Dir) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
|
||||||
|
out.Mode = 0555 | syscall.S_IFDIR
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *p9Dir) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
|
||||||
|
path := d.path
|
||||||
|
if path == "" {
|
||||||
|
path = "/"
|
||||||
|
}
|
||||||
|
fid, err := d.fsys.Open(path, plan9.OREAD)
|
||||||
|
if err != nil {
|
||||||
|
return nil, syscall.EIO
|
||||||
|
}
|
||||||
|
defer fid.Close()
|
||||||
|
dirs, err := fid.Dirreadall()
|
||||||
|
if err != nil {
|
||||||
|
return nil, syscall.EIO
|
||||||
|
}
|
||||||
|
entries := make([]fuse.DirEntry, len(dirs))
|
||||||
|
for i, dir := range dirs {
|
||||||
|
m := uint32(dir.Mode) & 0777
|
||||||
|
if dir.Mode&plan9.DMDIR != 0 {
|
||||||
|
m |= syscall.S_IFDIR
|
||||||
|
}
|
||||||
|
entries[i] = fuse.DirEntry{Name: dir.Name, Mode: m}
|
||||||
|
}
|
||||||
|
return fs.NewListDirStream(entries), 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *p9Dir) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
|
||||||
|
child := p9path(d.path, name)
|
||||||
|
dir, err := d.fsys.Stat(child)
|
||||||
|
if err != nil {
|
||||||
|
return nil, syscall.ENOENT
|
||||||
|
}
|
||||||
|
if dir.Mode&plan9.DMDIR != 0 {
|
||||||
|
out.Mode = uint32(dir.Mode)&0777 | syscall.S_IFDIR
|
||||||
|
node := &p9Dir{fsys: d.fsys, path: child}
|
||||||
|
return d.NewInode(ctx, node, fs.StableAttr{Mode: syscall.S_IFDIR}), 0
|
||||||
|
}
|
||||||
|
out.Mode = uint32(dir.Mode) & 0777
|
||||||
|
out.Size = dir.Length
|
||||||
|
node := &p9File{fsys: d.fsys, path: child}
|
||||||
|
return d.NewInode(ctx, node, fs.StableAttr{}), 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- p9File ---
|
||||||
|
|
||||||
|
func (f *p9File) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
|
||||||
|
dir, err := f.fsys.Stat(f.path)
|
||||||
|
if err != nil {
|
||||||
|
return syscall.ENOENT
|
||||||
|
}
|
||||||
|
// Report all files as rw so the kernel doesn't block writes;
|
||||||
|
// the 9P server enforces actual permissions.
|
||||||
|
out.Mode = 0666
|
||||||
|
out.Size = dir.Length
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *p9File) Setattr(ctx context.Context, fh fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno {
|
||||||
|
// Accept truncate silently — 9P files are append/overwrite on write.
|
||||||
|
out.Mode = 0666
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *p9File) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno) {
|
||||||
|
return nil, fuse.FOPEN_DIRECT_IO, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *p9File) Read(ctx context.Context, fh fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
|
||||||
|
f.mu.Lock()
|
||||||
|
defer f.mu.Unlock()
|
||||||
|
fid, err := f.fsys.Open(f.path, plan9.OREAD)
|
||||||
|
if err != nil {
|
||||||
|
return nil, syscall.EIO
|
||||||
|
}
|
||||||
|
defer fid.Close()
|
||||||
|
// The 9fans client doesn't support Seek; read from offset by reading and discarding.
|
||||||
|
// For small offsets this is fine; large seeks on big files would need a smarter approach.
|
||||||
|
if off > 0 {
|
||||||
|
buf := make([]byte, min(off, 8192))
|
||||||
|
remaining := off
|
||||||
|
for remaining > 0 {
|
||||||
|
n := int64(len(buf))
|
||||||
|
if n > remaining {
|
||||||
|
n = remaining
|
||||||
|
}
|
||||||
|
nr, err := fid.Read(buf[:n])
|
||||||
|
if err != nil || nr == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
remaining -= int64(nr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n, err := fid.Read(dest)
|
||||||
|
if err != nil && n == 0 {
|
||||||
|
return nil, syscall.EIO
|
||||||
|
}
|
||||||
|
return fuse.ReadResultData(dest[:n]), 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *p9File) Write(ctx context.Context, fh fs.FileHandle, data []byte, off int64) (uint32, syscall.Errno) {
|
||||||
|
f.mu.Lock()
|
||||||
|
defer f.mu.Unlock()
|
||||||
|
fid, err := f.fsys.Open(f.path, plan9.OWRITE)
|
||||||
|
if err != nil {
|
||||||
|
return 0, syscall.EACCES
|
||||||
|
}
|
||||||
|
n, err := fid.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
fid.Close()
|
||||||
|
return 0, syscall.EIO
|
||||||
|
}
|
||||||
|
if err := fid.Close(); err != nil {
|
||||||
|
return 0, syscall.EACCES
|
||||||
|
}
|
||||||
|
return uint32(n), 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func min(a, b int64) int64 {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
6
go.mod
6
go.mod
|
|
@ -4,9 +4,13 @@ go 1.25.6
|
||||||
|
|
||||||
require (
|
require (
|
||||||
9fans.net/go v0.0.7
|
9fans.net/go v0.0.7
|
||||||
|
github.com/hanwen/go-fuse/v2 v2.10.1
|
||||||
ollie v0.0.0-00010101000000-000000000000
|
ollie v0.0.0-00010101000000-000000000000
|
||||||
)
|
)
|
||||||
|
|
||||||
require gopkg.in/yaml.v3 v3.0.1 // indirect
|
require (
|
||||||
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
replace ollie => ../core
|
replace ollie => ../core
|
||||||
|
|
|
||||||
8
go.sum
8
go.sum
|
|
@ -3,6 +3,12 @@
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/hanwen/go-fuse/v2 v2.10.1 h1:QAqZuc9+aBtTou+OPruU/hkYQYCkgPtQd2QaepHkTTs=
|
||||||
|
github.com/hanwen/go-fuse/v2 v2.10.1/go.mod h1:aU7NkGYZUmuJrZapoI3mEcNve7PZTySUOLBuch/vR6U=
|
||||||
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
|
||||||
|
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
|
@ -25,6 +31,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210415045647-66c3f260301c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210415045647-66c3f260301c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
|
|
||||||
3
mkfile
3
mkfile
|
|
@ -4,8 +4,9 @@ all:V: install
|
||||||
|
|
||||||
build:V:
|
build:V:
|
||||||
go build -o $INSTALL_PATH/olliesrv .
|
go build -o $INSTALL_PATH/olliesrv .
|
||||||
|
go build -o $INSTALL_PATH/ollie-9p-mount ./cmd/ollie-9p-mount/
|
||||||
|
|
||||||
install:V: build
|
install:V: build
|
||||||
|
|
||||||
clean:V:
|
clean:V:
|
||||||
rm -f $INSTALL_PATH/olliesrv
|
rm -f $INSTALL_PATH/olliesrv $INSTALL_PATH/ollie-9p-mount
|
||||||
|
|
|
||||||
52
server.go
52
server.go
|
|
@ -207,6 +207,7 @@ type connState struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
pending map[uint16]context.CancelFunc // in-flight request cancels, keyed by tag
|
pending map[uint16]context.CancelFunc // in-flight request cancels, keyed by tag
|
||||||
|
uname string // user principal from Tattach
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server is the 9P server for ollie sessions.
|
// Server is the 9P server for ollie sessions.
|
||||||
|
|
@ -228,6 +229,7 @@ type Server struct {
|
||||||
tmpStore Store
|
tmpStore Store
|
||||||
strict bool
|
strict bool
|
||||||
yolo bool
|
yolo bool
|
||||||
|
groups map[string]map[string]bool // group → set of members
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerOption configures the 9P server.
|
// ServerOption configures the 9P server.
|
||||||
|
|
@ -260,6 +262,7 @@ func New(sink *olog.Sink, opts ...ServerOption) *Server {
|
||||||
skillStore: NewSkillStore(),
|
skillStore: NewSkillStore(),
|
||||||
transcriptStore: NewFlatDirStore(transcriptDir, 0444),
|
transcriptStore: NewFlatDirStore(transcriptDir, 0444),
|
||||||
tmpStore: NewFlatDirStore(tmpDir, 0600),
|
tmpStore: NewFlatDirStore(tmpDir, 0600),
|
||||||
|
groups: make(map[string]map[string]bool),
|
||||||
}
|
}
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(s)
|
o(s)
|
||||||
|
|
@ -293,6 +296,35 @@ func New(sink *olog.Sink, opts ...ServerOption) *Server {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddGroup adds a user to a group.
|
||||||
|
func (s *Server) AddGroup(group, user string) {
|
||||||
|
s.mu.Lock()
|
||||||
|
if s.groups[group] == nil {
|
||||||
|
s.groups[group] = make(map[string]bool)
|
||||||
|
}
|
||||||
|
s.groups[group][user] = true
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveGroup removes a user from a group.
|
||||||
|
func (s *Server) RemoveGroup(group, user string) {
|
||||||
|
s.mu.Lock()
|
||||||
|
if m := s.groups[group]; m != nil {
|
||||||
|
delete(m, user)
|
||||||
|
if len(m) == 0 {
|
||||||
|
delete(s.groups, group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// InGroup returns true if user is a member of group.
|
||||||
|
func (s *Server) InGroup(group, user string) bool {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
return s.groups[group][user]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// defaultTranscriptDir returns the transcript directory from OLLIE_TRANSCRIPT_PATH or the default.
|
// defaultTranscriptDir returns the transcript directory from OLLIE_TRANSCRIPT_PATH or the default.
|
||||||
func defaultTranscriptDir() string {
|
func defaultTranscriptDir() string {
|
||||||
|
|
@ -675,8 +707,10 @@ func boolToDir(isDir bool) uint32 {
|
||||||
func (s *Server) attach(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
|
func (s *Server) attach(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
|
||||||
cs.mu.Lock()
|
cs.mu.Lock()
|
||||||
defer cs.mu.Unlock()
|
defer cs.mu.Unlock()
|
||||||
|
cs.uname = fc.Uname
|
||||||
qid := plan9.Qid{Type: QTDir, Path: 0}
|
qid := plan9.Qid{Type: QTDir, Path: 0}
|
||||||
cs.fids[fc.Fid] = &fid{path: "/", qid: qid}
|
cs.fids[fc.Fid] = &fid{path: "/", qid: qid}
|
||||||
|
s.log.Debug("Tattach uname=%q", fc.Uname)
|
||||||
return &plan9.Fcall{Type: plan9.Rattach, Tag: fc.Tag, Qid: qid}
|
return &plan9.Fcall{Type: plan9.Rattach, Tag: fc.Tag, Qid: qid}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1181,13 +1215,21 @@ func (s *Server) clunk(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
|
||||||
}
|
}
|
||||||
delete(cs.fids, fc.Fid)
|
delete(cs.fids, fc.Fid)
|
||||||
}
|
}
|
||||||
|
uname := cs.uname
|
||||||
cs.mu.Unlock()
|
cs.mu.Unlock()
|
||||||
if writable {
|
if writable {
|
||||||
s.log.Debug("Tclunk flush path=%q writeBuf=%d", path, len(data))
|
s.log.Debug("Tclunk flush path=%q writeBuf=%d uname=%q", path, len(data), uname)
|
||||||
input := strings.TrimSpace(string(data))
|
input := strings.TrimSpace(string(data))
|
||||||
|
// Reject self-prompt before async dispatch so the error reaches the client.
|
||||||
|
if uname != "" && strings.HasPrefix(path, "/s/") {
|
||||||
|
parts := strings.SplitN(strings.TrimPrefix(path, "/"), "/", 3)
|
||||||
|
if len(parts) == 3 && parts[2] == "prompt" && uname == parts[1] {
|
||||||
|
return errFcall(fc, "self-prompt rejected")
|
||||||
|
}
|
||||||
|
}
|
||||||
if s.isAsyncWrite(path) {
|
if s.isAsyncWrite(path) {
|
||||||
go s.handleWrite(path, input) //nolint:errcheck
|
go s.handleWrite(path, input, uname) //nolint:errcheck
|
||||||
} else if err := s.handleWrite(path, input); err != nil {
|
} else if err := s.handleWrite(path, input, uname); err != nil {
|
||||||
s.log.Debug("Tclunk handleWrite err=%v", err)
|
s.log.Debug("Tclunk handleWrite err=%v", err)
|
||||||
return errFcall(fc, err.Error())
|
return errFcall(fc, err.Error())
|
||||||
}
|
}
|
||||||
|
|
@ -1269,8 +1311,8 @@ func (s *Server) remove(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
|
||||||
// handleWrite processes a fully-assembled write payload for the given path.
|
// handleWrite processes a fully-assembled write payload for the given path.
|
||||||
// Called synchronously from clunk; prompt writes are the exception (spawned
|
// Called synchronously from clunk; prompt writes are the exception (spawned
|
||||||
// as a goroutine because they block for the entire agent turn).
|
// as a goroutine because they block for the entire agent turn).
|
||||||
func (s *Server) handleWrite(path, input string) error {
|
func (s *Server) handleWrite(path, input, uname string) error {
|
||||||
s.log.Debug("handleWrite path=%q input_len=%d", path, len(input))
|
s.log.Debug("handleWrite path=%q input_len=%d uname=%q", path, len(input), uname)
|
||||||
|
|
||||||
if path == "/s/new" {
|
if path == "/s/new" {
|
||||||
if input == "" {
|
if input == "" {
|
||||||
|
|
|
||||||
Reference in New Issue