diff options
| author | Sam Anthony <sam@samanthony.xyz> | 2026-03-07 18:33:37 -0500 |
|---|---|---|
| committer | Sam Anthony <sam@samanthony.xyz> | 2026-03-07 18:33:37 -0500 |
| commit | 79f55b0b233258623062db3c916783b4d14bf14a (patch) | |
| tree | 09881b5482d2e61d29c8589f2cfabb757252f89b /back | |
| parent | ade134e5e6aaf28dd65d87dc493d05253585559b (diff) | |
| download | buth-79f55b0b233258623062db3c916783b4d14bf14a.zip | |
authfs: implement p9p.Session
Diffstat (limited to 'back')
| -rw-r--r-- | back/auth/auth.go | 17 | ||||
| -rw-r--r-- | back/cmd/authfs/main.go | 66 | ||||
| -rw-r--r-- | back/cmd/authfs/root.go | 21 | ||||
| -rw-r--r-- | back/cmd/authfs/session.go | 187 |
4 files changed, 291 insertions, 0 deletions
diff --git a/back/auth/auth.go b/back/auth/auth.go new file mode 100644 index 0000000..5685001 --- /dev/null +++ b/back/auth/auth.go @@ -0,0 +1,17 @@ +package auth + +import "fmt" + +const ( + // Maximum number of bytes in a Username. + MaxUsernameSize = 64 +) + +type Username string + +func ValidiateUsername(s string) (Username, error) { + if len(s) > MaxUsernameSize { + return "", fmt.Errorf("username longer than %d bytes: %q", MaxUsernameSize, s) + } + return Username(s), nil +} diff --git a/back/cmd/authfs/main.go b/back/cmd/authfs/main.go new file mode 100644 index 0000000..f8618df --- /dev/null +++ b/back/cmd/authfs/main.go @@ -0,0 +1,66 @@ +/* +This is the authfs daemon. It stores the user database and client +sessions, and it serves a 9P filesystem for managing them. + +User data (usernames and password hashes) are stored on disk. Sessions +are stored in memory. +*/ +package main + +import ( + "context" + "flag" + "log" + "net" + "os" + + "github.com/docker-archive/go-p9p" +) + +const ( + defaultNetwork = "unix" + defaultAddress = "authfs.sock" +) + +var ( + network, address string + + outlog = log.New(os.Stdout, "authfs [info]: ", log.LstdFlags) + errlog = log.New(os.Stderr, "authfs [error]: ", log.LstdFlags|log.Llongfile) +) + +func main() { + flag.StringVar(&network, "net", defaultNetwork, "network transport protocol: {tcp, unix}") + flag.StringVar(&address, "addr", defaultAddress, "IP address or Unix socket path to listen on") + flag.Parse() + + ln, err := net.Listen(network, address) + if err != nil { + errlog.Fatal(err) + } + defer logErr(ln.Close()) + + for { + if conn, err := ln.Accept(); err == nil { + outlog.Println("connected", conn.RemoteAddr()) + go handle(conn) + } else { + errlog.Println(err) + } + } +} + +func handle(conn net.Conn) { + defer logErr(conn.Close()) + ctx := context.Background() + handler := p9p.Dispatch(NewSession()) + if err := p9p.ServeConn(ctx, conn, handler); err != nil { + errlog.Printf("%v: %v\n", conn.RemoteAddr(), err) + } +} + +func logErr(err error) { + if err != nil { + errlog.Println(err) + } +} diff --git a/back/cmd/authfs/root.go b/back/cmd/authfs/root.go new file mode 100644 index 0000000..70ca643 --- /dev/null +++ b/back/cmd/authfs/root.go @@ -0,0 +1,21 @@ +package main + +const rootQid = p9p.Qid{ + Type: QTDIR, + Version: 0x0, + Path: 0x1, +} + +// Root is the root Dir which contains /users and /sessions. +type Root struct { + path string +} + +func (r Root) Qid() p9p.Qid { return rootQid } + +func (r Root) Stat() (p9p.Dir, error) { + return p9p.Dir{ + Qid: rootQid, + Mode: DMDIR | DMREAD, + +} diff --git a/back/cmd/authfs/session.go b/back/cmd/authfs/session.go new file mode 100644 index 0000000..2d4eb98 --- /dev/null +++ b/back/cmd/authfs/session.go @@ -0,0 +1,187 @@ +package main + +import ( + "context" + "sync" + + "github.com/docker-archive/go-p9p" +) + +// Session implements the p9p.Session interface. +type Session struct { + mu sync.Mutex + fids map[p9p.Fid]Fid + *Root +} + +type Fid struct { + File + uname string +} + +type File interface { + Qid() p9p.Qid + Stat() (p9p.Dir, error) + Open(mode p9p.Flag) error + Remove() error + Read(ctx context.Context, p []byte, offset int64) (n int, err error) + Write(ctx context.Context, p []byte, offset int64) (n int, err error) +} + +type Dir interface { + File + Create(ctx context.Context, name string, perm uint32, mode p9p.Flag) (File, error) + Walk(ctx context.Context, name string) (File, error) +} + +func NewSession(root *Root) p9p.Session { + return &Session{ + fids: make(map[p9p.Fid]Fid), + Root: root, + } +} + +func (s *Session) Auth(ctx context.Context, afid p9p.Fid, uname, aname string) (p9p.Qid, error) { + return p9p.Qid{}, p9p.MessageRerror{"authfs: no authentication required"} +} + +func (s *Session) Attach(ctx context.Context, fid, afid p9p.Fid, uname, aname string) (p9p.Qid, error) { + s.mu.Lock() + defer s.mu.Unlock() + + if _, exists := s.fids[fid]; exists { + return p9p.Qid{}, p9p.ErrDupfid + } + f := Fid{s.root, uname} + s.fids[fid] = f + return f.Qid(), nil +} + +func (s *Session) Clunk(ctx context.Context, fid p9p.Fid) error { + s.mu.Lock() + defer s.mu.Unlock() + + if _, ok := s.fids[fid]; ok { + delete(s.fids, fid) + return nil + } else { + return p9p.ErrUnknownfid + } +} + +func (s *Session) Remove(ctx context.Context, fid p9p.Fid) error { + s.mu.Lock() + defer s.mu.Unlock() + + f, ok := s.fids[fid] + if !ok { + return p9p.ErrUnknownfid + } + delete(s.fids, fid) // clunk + return f.Remove() +} + +func (s *Session) Walk(ctx context.Context, fid, newfid p9p.Fid, names ...string) ([]p9p.Qid, error) { + s.mu.Lock() + defer s.mu.Unlock() + + f, ok := s.fids[fid] + if !ok { + return nil, p9p.ErrUnknownfid + } + + if _, exists := s.fids[newfid]; exists { + return nil, p9p.ErrDupfid + } + + var ( + qids []p9p.Qid + err error + dot = f + ) + defer func() { s.fids[newfid] = dot }() + for _, name := range names { + dir, ok := dot.File.(Dir) + if !ok { + return qids, p9p.ErrWalknodir + } + dot.File, err = dir.Walk(ctx, name) + if err != nil { + return qids, err + } + qids = append(qids, dot.File.Qid()) + } + return qids, nil +} + +func (s *Session) Read(ctx context.Context, fid p9p.Fid, p []byte, offset int64) (n int, err error) { + s.mu.Lock() + defer s.mu.Unlock() + + if f, ok := s.fids[fid]; ok { + return f.File.Read(ctx, p, offset) + } else { + return 0, p9p.ErrUnknownfid + } +} + +func (s *Session) Write(ctx context.Context, fid p9p.Fid, p []byte, offset int64) (n int, err error) { + s.mu.Lock() + defer s.mu.Unlock() + + if f, ok := s.fids[fid]; ok { + return f.File.Write(ctx, p, offset) + } else { + return 0, p9p.ErrUnknownfid + } +} + +func (s *Session) Open(ctx context.Context, fid p9p.Fid, mode p9p.Flag) (p9p.Qid, uint32, error) { + s.mu.Lock() + defer s.mu.Unlock() + + f, ok := s.fids[fid] + if !ok { + return p9p.Qid{}, 0, p9p.ErrUnknownfid + } + if err := f.Open(mode); err != nil { + return p9p.Qid{}, 0, err + } + return f.File.Qid(), 0, nil +} + +func (s *Session) Create(ctx context.Context, parent p9p.Fid, name string, perm uint32, mode p9p.Flag) (p9p.Qid, uint32, error) { + s.mu.Lock() + defer s.mu.Unlock() + + f, ok := s.fids[parent] + if !ok { + return p9p.Qid{}, 0, p9p.ErrUnknownfid + } + dir, ok := f.File.(Dir) + if !ok { + return p9p.Qid{}, 0, p9p.ErrCreatenondir + } + + child, err := dir.Create(ctx, name, perm, mode) + return child.Qid(), 0, err +} + +func (s *Session) Stat(ctx context.Context, fid p9p.Fid) (p9p.Dir, error) { + s.mu.Lock() + defer s.mu.Unlock() + + if f, ok := s.fids[fid]; ok { + return f.File.Stat() + } else { + return p9p.Dir{}, p9p.ErrUnknownfid + } +} + +func (s *Session) WStat(ctx context.Context, fid p9p.Fid, dir p9p.Dir) error { + return p9p.ErrPerm +} + +func (s *Session) Version() (msize int, version string) { + return p9p.DefaultMSize, p9p.DefaultVersion +} |