summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--OVERSSH10
-rw-r--r--cmd/tunneld/main.go2
-rw-r--r--pkg/netstring/netstring.go22
-rw-r--r--pkg/server/hook/auth.go186
-rw-r--r--pkg/server/hook/proxy.go2
-rw-r--r--pkg/server/socket/socket.go4
-rw-r--r--pkg/test/auth_test.go68
-rw-r--r--pkg/test/defer_test.go2
-rw-r--r--pkg/test/exec_test.go2
-rw-r--r--pkg/test/hook_test.go4
-rw-r--r--pkg/test/proxy_test.go4
-rwxr-xr-xtest/auth.sh18
-rwxr-xr-xtest/proxy.sh4
13 files changed, 250 insertions, 78 deletions
diff --git a/OVERSSH b/OVERSSH
deleted file mode 100644
index 2300147..0000000
--- a/OVERSSH
+++ /dev/null
@@ -1,10 +0,0 @@
-- make original.addr and original.port variables
-
-add name dirent
- listen,addr=-:606,redirect \
- proxy,addr=@[original] \
- proxy,addr=@[original.addr]:22 \
- dial,addr=@over
-
-add sslh to host
-add local tunneld: add name proxy listen,addr=-:604 proxy
diff --git a/cmd/tunneld/main.go b/cmd/tunneld/main.go
index 55783b6..7fbc8a5 100644
--- a/cmd/tunneld/main.go
+++ b/cmd/tunneld/main.go
@@ -40,7 +40,7 @@ func initLog() {
log.Fatal("bad usage: duplicate log write flag")
}
- if *debugFlag && *syslogFlag {
+ if *debugFlag && *syslogFlag {
log.Fatal("bad usage: debug with syslog is out of sense")
}
diff --git a/pkg/netstring/netstring.go b/pkg/netstring/netstring.go
index 62507f9..00722a8 100644
--- a/pkg/netstring/netstring.go
+++ b/pkg/netstring/netstring.go
@@ -59,3 +59,25 @@ func (d *Decoder) Decode() (out string, err error) {
return string(buf[:n]), nil
}
+
+func Encode(w io.Writer, args ...string) error {
+ e := Encoder{w: w}
+ for _, s := range args {
+ if err := e.Encode(s); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func Decode(r io.Reader, args ...*string) error {
+ d := Decoder{r: r}
+ for _, s := range args {
+ t, err := d.Decode()
+ if err != nil {
+ return err
+ }
+ *s = t
+ }
+ return nil
+}
diff --git a/pkg/server/hook/auth.go b/pkg/server/hook/auth.go
index 21f900e..0f70d88 100644
--- a/pkg/server/hook/auth.go
+++ b/pkg/server/hook/auth.go
@@ -1,11 +1,14 @@
package hook
import (
+ "bufio"
"crypto/md5"
"crypto/rand"
"errors"
+ "fmt"
"io"
- "sync"
+ "os"
+ "strings"
"time"
"tunnel/pkg/server/env"
@@ -17,62 +20,109 @@ const authTimeout = 5 * time.Second
const saltSize = 16
const hashSize = md5.Size
+var hashNone = strings.Repeat("\x00", hashSize)
+
type authHook struct {
- m sync.Map
+ Passive bool
}
type auth struct {
- h *authHook
-
- secret string
+ file *os.File
+ user string
+ salt string
+ pass string
- salt struct {
- self string
- peer string
+ peer struct {
+ user string
+ salt string
}
hash string
- readSaltDone chan struct{}
- readHashDone chan struct{}
+ passive bool
+
+ stageHello chan struct{}
+ stageHash chan struct{}
ready chan struct{}
- cancel chan struct{}
+ recver chan struct{}
+ sender chan struct{}
tmr *time.Timer
}
-var errDupSalt = errors.New("peer repeats salt")
-var errAuthFail = errors.New("peer auth fail")
+var errAuthFail = errors.New("mismatch auth hash")
+var errAuthUnknown = errors.New("unknown auth peer")
var errTimeout = errors.New("timeout")
-func (a *auth) Init() error {
- b := make([]byte, saltSize)
- if _, err := rand.Read(b); err != nil {
- return err
+func hashsum(args ...string) string {
+ h := md5.New()
+
+ for _, s := range args {
+ io.WriteString(h, s)
}
- a.salt.self = string(b)
+ return string(h.Sum(nil))
+}
- a.h.m.Store(a.salt.self, struct{}{})
+func getpass(f *os.File, salt string, user string) string {
+ f.Seek(0, 0)
- return nil
+ for scanner := bufio.NewScanner(f); scanner.Scan(); {
+ splitted := strings.SplitN(scanner.Text(), "#", 2)
+ tokens := strings.Fields(splitted[0])
+ if len(tokens) > 1 {
+ if salt == "" {
+ if tokens[0] == user {
+ return tokens[1]
+ }
+ } else {
+ if hashsum(salt, tokens[0]) == user {
+ return tokens[1]
+ }
+ }
+ }
+ }
+
+ return ""
}
-func (a *auth) hashSum(c string) string {
- h := md5.New()
+func (a *auth) Init(authfile string, user string) error {
+ file, err := os.Open(authfile)
+ if err != nil {
+ return fmt.Errorf("authfile: %w", err)
+ }
- io.WriteString(h, c)
- io.WriteString(h, a.secret)
+ a.file = file
- return string(h.Sum(nil))
+ if user != "" {
+ pass := getpass(file, "", user)
+ if pass == "" {
+ file.Close()
+ return fmt.Errorf("authfile: no pass for user '%s'", user)
+ }
+ a.user = user
+ a.pass = pass
+ }
+
+ b := make([]byte, saltSize)
+ if _, err := rand.Read(b); err != nil {
+ return err
+ }
+ a.salt = string(b)
+
+ a.tmr = time.NewTimer(authTimeout)
+
+ return nil
}
func (a *auth) sync(c chan struct{}) error {
select {
case <-a.tmr.C:
return errTimeout
- case <-a.cancel:
+ case <-a.recver:
+ return io.EOF
+ case <-a.sender:
return io.EOF
case <-c:
return nil
@@ -82,24 +132,46 @@ func (a *auth) sync(c chan struct{}) error {
func (a *auth) Send(rq, wq queue.Q) error {
w := wq.Writer()
- io.WriteString(w, a.salt.self)
+ io.WriteString(w, a.salt)
+ if a.passive {
+ io.WriteString(w, hashNone)
+ } else {
+ io.WriteString(w, hashsum(a.salt, a.user))
+ }
- if err := a.sync(a.readSaltDone); err != nil {
+ if err := a.sync(a.stageHello); err != nil {
return err
}
- if _, ok := a.h.m.Load(a.salt.peer); ok {
- return errDupSalt
+ if a.passive && a.peer.user == hashNone {
+ close(a.sender)
+ return fmt.Errorf("can not be passive together")
}
- io.WriteString(w, a.hashSum(a.salt.peer))
+ var pass string
- if err := a.sync(a.readHashDone); err != nil {
+ if a.peer.user == hashNone {
+ pass = hashsum(a.pass)
+ } else {
+ pass = getpass(a.file, a.peer.salt, a.peer.user)
+ if pass == "" {
+ close(a.sender)
+ return fmt.Errorf("no pass for peer")
+ }
+
+ if a.passive {
+ a.pass = hashsum(pass)
+ }
+ }
+
+ io.WriteString(w, hashsum(a.peer.salt, pass))
+
+ if err := a.sync(a.stageHash); err != nil {
return err
}
- if a.hash != a.hashSum(a.salt.self) {
- close(a.cancel)
+ if a.hash != hashsum(a.salt, a.pass) {
+ close(a.sender)
return errAuthFail
}
@@ -112,29 +184,32 @@ func (a *auth) read(r io.Reader, n int, s *string) error {
b := make([]byte, n)
if _, err := io.ReadFull(r, b); err != nil {
- close(a.cancel)
+ close(a.recver)
return err
}
*s = string(b)
-
return nil
}
func (a *auth) Recv(rq, wq queue.Q) error {
r := rq.Reader()
- if err := a.read(r, saltSize, &a.salt.peer); err != nil {
+ if err := a.read(r, saltSize, &a.peer.salt); err != nil {
return err
}
- close(a.readSaltDone)
+ if err := a.read(r, hashSize, &a.peer.user); err != nil {
+ return err
+ }
+
+ close(a.stageHello)
if err := a.read(r, hashSize, &a.hash); err != nil {
return err
}
- close(a.readHashDone)
+ close(a.stageHash)
if err := a.sync(a.ready); err != nil {
return err
@@ -146,23 +221,38 @@ func (a *auth) Recv(rq, wq queue.Q) error {
}
func (a *auth) Close() {
- a.h.m.Delete(a.salt.self)
+ a.file.Close()
}
func (h *authHook) New(env env.Env) (interface{}, error) {
+ file := env.Value("authfile")
+
+ if file == "" {
+ return nil, errors.New("no authfile configured")
+ }
+
+ user := ""
+
+ if !h.Passive {
+ user = env.Value("authuser")
+
+ if user == "" {
+ return nil, errors.New("no authuser configured")
+ }
+ }
+
a := &auth{
- h: h,
- secret: env.Value("secret"),
- tmr: time.NewTimer(authTimeout),
+ passive: h.Passive,
- cancel: make(chan struct{}),
ready: make(chan struct{}),
+ recver: make(chan struct{}),
+ sender: make(chan struct{}),
- readSaltDone: make(chan struct{}),
- readHashDone: make(chan struct{}),
+ stageHello: make(chan struct{}),
+ stageHash: make(chan struct{}),
}
- if err := a.Init(); err != nil {
+ if err := a.Init(file, user); err != nil {
return nil, err
}
@@ -170,5 +260,5 @@ func (h *authHook) New(env env.Env) (interface{}, error) {
}
func init() {
- register("auth", "chap authentication out/in", authHook{})
+ register("auth", "chap out/in", authHook{})
}
diff --git a/pkg/server/hook/proxy.go b/pkg/server/hook/proxy.go
index 0a79056..4e30105 100644
--- a/pkg/server/hook/proxy.go
+++ b/pkg/server/hook/proxy.go
@@ -11,7 +11,7 @@ import (
"tunnel/pkg/server/queue"
)
-var addrPattern = "^([0-9a-zA-Z-.]+|\\[[0-9a-fA-F:]*\\]):[0-9]+$"
+var addrPattern = "^(([0-9a-zA-Z-.]+|\\[[0-9a-fA-F:]*\\]):|%)[0-9]+$"
var isGoodAddr = regexp.MustCompile(addrPattern).MatchString
type proxyHook struct {
diff --git a/pkg/server/socket/socket.go b/pkg/server/socket/socket.go
index cb76cf7..6768c48 100644
--- a/pkg/server/socket/socket.go
+++ b/pkg/server/socket/socket.go
@@ -103,8 +103,8 @@ func New(desc string, e env.Env) (S, error) {
func parseProtoAddr(proto, addr string) (string, string) {
if proto == "tcp" || proto == "udp" {
- if strings.HasPrefix(addr, "-:") {
- addr = "localhost" + addr[1:]
+ if strings.HasPrefix(addr, "%") {
+ addr = "localhost:" + addr[1:]
}
}
diff --git a/pkg/test/auth_test.go b/pkg/test/auth_test.go
index dedafa8..1741d68 100644
--- a/pkg/test/auth_test.go
+++ b/pkg/test/auth_test.go
@@ -2,6 +2,8 @@ package test
import (
"testing"
+ "fmt"
+ "os"
)
func TestAuthHook(t *testing.T) {
@@ -10,11 +12,69 @@ func TestAuthHook(t *testing.T) {
c := e.newInstance()
- c.Exec("add name T listen,addr=-:0 auth aes dial,addr=@[tunnel.X.listen]")
- c.Exec("add name X listen,addr=-:0 /aes /auth dial,addr=@[addr]")
+ var secrets string
- c.Exec("set tunnel.X.secret secret")
- c.Exec("set tunnel.T.secret secret")
+ f, err := os.CreateTemp("", "test-auth-")
+ if err != nil {
+ e.Fatalf("create temp: %v", err)
+ }
+
+ secrets = f.Name()
+
+ fmt.Fprintln(f, "T t")
+ fmt.Fprintln(f, "X x")
+ f.Close()
+
+ defer os.Remove(secrets)
+
+ c.Exec("add name T listen,addr=%%0 auth aes dial,addr=@[tunnel.X.listen]")
+ c.Exec("add name X listen,addr=%%0 /aes /auth dial,addr=@[addr]")
+
+ c.Exec("set authfile %s", secrets)
+ c.Exec("set tunnel.T.authuser T")
+ c.Exec("set tunnel.X.authuser X")
+
+ listen := e.Listen("tcp", "127.0.0.1:0")
+ c.Set("addr", listen.Addr())
+
+ out := e.Dial("tcp", c.Get("tunnel.T.listen"))
+ in := e.Accept(listen)
+
+ e.Write(out, dummy)
+
+ buf := make([]byte, len(dummy))
+ e.ReadFull(in, buf)
+
+ if r := string(buf); r != dummy {
+ e.Fatalf("wrong reply: send '%s', recv '%s'", dummy, r)
+ }
+}
+
+func TestAuthPassiveHook(t *testing.T) {
+ e := newEnv(t)
+ defer e.Free()
+
+ c := e.newInstance()
+
+ var secrets string
+
+ f, err := os.CreateTemp("", "test-auth-passive-")
+ if err != nil {
+ e.Fatalf("create temp: %v", err)
+ }
+
+ secrets = f.Name()
+
+ fmt.Fprintln(f, "T t")
+ f.Close()
+
+ defer os.Remove(secrets)
+
+ c.Exec("add name T listen,addr=%%0 auth aes dial,addr=@[tunnel.X.listen]")
+ c.Exec("add name X listen,addr=%%0 /aes /auth,passive dial,addr=@[addr]")
+
+ c.Exec("set authfile %s", secrets)
+ c.Exec("set tunnel.T.authuser T")
listen := e.Listen("tcp", "127.0.0.1:0")
c.Set("addr", listen.Addr())
diff --git a/pkg/test/defer_test.go b/pkg/test/defer_test.go
index 57854b2..3e51571 100644
--- a/pkg/test/defer_test.go
+++ b/pkg/test/defer_test.go
@@ -10,7 +10,7 @@ func TestDeferSocket(t *testing.T) {
c := e.newInstance()
- c.Exec("add name T listen,addr=-:0 defer,addr=@[addr]")
+ c.Exec("add name T listen,addr=%%0 defer,addr=@[addr]")
out := e.Dial("tcp", c.Get("tunnel.T.listen"))
diff --git a/pkg/test/exec_test.go b/pkg/test/exec_test.go
index c8d61a0..028b206 100644
--- a/pkg/test/exec_test.go
+++ b/pkg/test/exec_test.go
@@ -12,7 +12,7 @@ func TestExec(t *testing.T) {
c := e.newInstance()
- c.Exec("add name T listen,addr=-:0 upper exec,cmd=cat")
+ c.Exec("add name T listen,addr=%%0 upper exec,cmd=cat")
conn := e.Dial("tcp", c.Get("tunnel.T.listen"))
diff --git a/pkg/test/hook_test.go b/pkg/test/hook_test.go
index 7808883..8e4fa4d 100644
--- a/pkg/test/hook_test.go
+++ b/pkg/test/hook_test.go
@@ -13,7 +13,7 @@ func TestUpperHook(t *testing.T) {
c := e.newInstance()
- c.Exec("add name T listen,addr=-:0 upper loop")
+ c.Exec("add name T listen,addr=%%0 upper loop")
conn := e.Dial("tcp", c.Get("tunnel.T.listen"))
@@ -33,7 +33,7 @@ func TestHexHook(t *testing.T) {
c := e.newInstance()
- c.Exec("add name T listen,addr=-:0 hex dial,addr=@[addr]")
+ c.Exec("add name T listen,addr=%%0 hex dial,addr=@[addr]")
listen := e.Listen("tcp", "127.0.0.1:0")
c.Set("addr", listen.Addr())
diff --git a/pkg/test/proxy_test.go b/pkg/test/proxy_test.go
index ae89b0a..46782d2 100644
--- a/pkg/test/proxy_test.go
+++ b/pkg/test/proxy_test.go
@@ -10,8 +10,8 @@ func TestProxyHook(t *testing.T) {
c := e.newInstance()
- c.Exec("add name C listen,addr=-:0 proxy,addr=@[addr] dial,addr=@[tunnel.S.listen]")
- c.Exec("add name S listen,addr=-:0 proxy")
+ c.Exec("add name C listen,addr=%%0 proxy,addr=@[addr] dial,addr=@[tunnel.S.listen]")
+ c.Exec("add name S listen,addr=%%0 proxy")
c.Exec("set tunnel.S.proxy.auth user:password")
c.Exec("set tunnel.C.proxy.auth user:password")
diff --git a/test/auth.sh b/test/auth.sh
index d8a1dee..608b4fd 100755
--- a/test/auth.sh
+++ b/test/auth.sh
@@ -1,12 +1,22 @@
#!/bin/bash
+FILE=`mktemp`
+
+cat > $FILE <<EOF
+T t
+X x
+EOF
+
+trap 'unlink $FILE' EXIT
+
ROOT=$(dirname $0)/..
PATH=$ROOT/bin:$PATH
-tunnel add name T listen,addr=-:2000 auth aes dial,addr=-:3000
-tunnel add name X listen,addr=-:3000 -aes -auth dial,addr=-:4000
-tunnel set tunnel.T.secret secret
-tunnel set tunnel.X.secret secret
+tunnel add name T listen,addr=%2000 auth dump aes dial,addr=%3000
+tunnel add name X listen,addr=%3000 /aes /auth dial,addr=%4000
+tunnel set authfile $FILE
+tunnel set tunnel.T.authuser T
+tunnel set tunnel.X.authuser X
nc -l 4000 &
echo "Hello, World!" | nc -N localhost 2000
wait
diff --git a/test/proxy.sh b/test/proxy.sh
index 8348bd6..e8c5a3f 100755
--- a/test/proxy.sh
+++ b/test/proxy.sh
@@ -3,8 +3,8 @@
ROOT=$(dirname $0)/..
PATH=$ROOT/bin:$PATH
-tunnel add name C listen,addr=-:2000 proxy,addr=-:4000 dial,addr=-:3000
-tunnel add name S listen,addr=-:3000 proxy
+tunnel add name C listen,addr=%2000 proxy,addr=%4000 dial,addr=%3000
+tunnel add name S listen,addr=%3000 proxy
tunnel set tunnel.C.proxy.auth user:password
tunnel set tunnel.S.proxy.auth user:password
nc -l 4000 &