ollie/experiments/auth9p/server.go

217 lines
4.3 KiB
Go

// Server: 9P server with Tauth-based authentication
package main
import (
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"log"
"net"
"os"
"sync"
"9fans.net/go/plan9"
)
type Server struct {
mu sync.Mutex
secret string // set on first auth, verified on subsequent
token string // session token returned after auth
}
func main() {
sockPath := "/tmp/auth9p-test.sock"
os.Remove(sockPath)
ln, err := net.Listen("unix", sockPath)
if err != nil {
log.Fatal(err)
}
defer ln.Close()
defer os.Remove(sockPath)
fmt.Printf("Server listening on %s\n", sockPath)
fmt.Println("Waiting for connections...")
srv := &Server{}
for {
conn, err := ln.Accept()
if err != nil {
log.Printf("accept: %v", err)
continue
}
go srv.handle(conn)
}
}
func (s *Server) handle(conn net.Conn) {
defer conn.Close()
fmt.Printf("New connection from %v\n", conn.RemoteAddr())
// Track fids
fids := make(map[uint32]*Fid)
authFids := make(map[uint32]*AuthFid)
for {
// Read 9P message
fc, err := plan9.ReadFcall(conn)
if err != nil {
if err != io.EOF {
log.Printf("read: %v", err)
}
return
}
fmt.Printf("← %v\n", fc)
var resp *plan9.Fcall
switch fc.Type {
case plan9.Tversion:
resp = &plan9.Fcall{
Type: plan9.Rversion,
Tag: fc.Tag,
Msize: fc.Msize,
Version: "9P2000",
}
case plan9.Tauth:
// Create auth fid
authFids[fc.Afid] = &AuthFid{
uname: fc.Uname,
aname: fc.Aname,
}
resp = &plan9.Fcall{
Type: plan9.Rauth,
Tag: fc.Tag,
Qid: plan9.Qid{Type: plan9.QTAUTH, Vers: 0, Path: uint64(fc.Afid)},
}
case plan9.Tattach:
// Check auth
af, ok := authFids[fc.Afid]
if !ok || !af.authenticated {
resp = errorResp(fc.Tag, "not authenticated")
break
}
fids[fc.Fid] = &Fid{path: "/"}
resp = &plan9.Fcall{
Type: plan9.Rattach,
Tag: fc.Tag,
Qid: plan9.Qid{Type: plan9.QTDIR, Vers: 0, Path: 0},
}
fmt.Printf("✓ Client attached successfully (token: %s)\n", af.token)
case plan9.Tread:
// Reading from auth fid returns the token
if af, ok := authFids[fc.Fid]; ok {
if !af.authenticated {
resp = errorResp(fc.Tag, "write secret first")
} else {
data := []byte(af.token + "\n")
resp = &plan9.Fcall{
Type: plan9.Rread,
Tag: fc.Tag,
Data: data,
Count: uint32(len(data)),
}
}
} else {
resp = &plan9.Fcall{
Type: plan9.Rread,
Tag: fc.Tag,
Data: []byte("hello from server\n"),
Count: 18,
}
}
case plan9.Twrite:
// Writing to auth fid sets/verifies secret
if af, ok := authFids[fc.Fid]; ok {
clientSecret := string(fc.Data)
resp = s.handleAuthWrite(fc.Tag, af, clientSecret)
} else {
resp = &plan9.Fcall{
Type: plan9.Rwrite,
Tag: fc.Tag,
Count: fc.Count,
}
}
case plan9.Tclunk:
delete(fids, fc.Fid)
delete(authFids, fc.Fid)
resp = &plan9.Fcall{Type: plan9.Rclunk, Tag: fc.Tag}
default:
resp = errorResp(fc.Tag, fmt.Sprintf("unhandled message type %d", fc.Type))
}
fmt.Printf("→ %v\n", resp)
if err := plan9.WriteFcall(conn, resp); err != nil {
log.Printf("write: %v", err)
return
}
}
}
func (s *Server) handleAuthWrite(tag uint16, af *AuthFid, clientSecret string) *plan9.Fcall {
s.mu.Lock()
defer s.mu.Unlock()
if s.secret == "" {
// First auth - set the secret
s.secret = clientSecret
s.token = randomToken()
af.authenticated = true
af.token = s.token
fmt.Printf("✓ Secret established (first connection)\n")
return &plan9.Fcall{
Type: plan9.Rwrite,
Tag: tag,
Count: uint32(len(clientSecret)),
}
}
// Subsequent auth - verify secret
if clientSecret != s.secret {
fmt.Printf("✗ Secret mismatch!\n")
return errorResp(tag, "authentication failed")
}
af.authenticated = true
af.token = s.token
fmt.Printf("✓ Secret verified (reconnection)\n")
return &plan9.Fcall{
Type: plan9.Rwrite,
Tag: tag,
Count: uint32(len(clientSecret)),
}
}
type Fid struct {
path string
}
type AuthFid struct {
uname string
aname string
authenticated bool
token string
}
func errorResp(tag uint16, msg string) *plan9.Fcall {
return &plan9.Fcall{
Type: plan9.Rerror,
Tag: tag,
Ename: msg,
}
}
func randomToken() string {
b := make([]byte, 16)
rand.Read(b)
return hex.EncodeToString(b)
}