217 lines
4.3 KiB
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)
|
|
}
|