package hook import ( "crypto/md5" "crypto/rand" "errors" "io" "tunnel/pkg/netstring" "tunnel/pkg/server/env" "tunnel/pkg/server/opts" "tunnel/pkg/server/queue" ) const ChallengeLen = 16 type auth struct { secret string challenge struct { self string peer string } hash string recvChallenge chan struct{} recvHash chan struct{} fail chan struct{} ok chan struct{} } var errDupChallenge = errors.New("peer duplicates challenge") var errAuthFail = errors.New("peer auth fail") type authHook struct{} func (a *auth) generateChallenge() error { b := make([]byte, ChallengeLen) if _, err := rand.Read(b); err != nil { return err } a.challenge.self = string(b) return nil } func (a *auth) getHash(c string) string { h := md5.New() io.WriteString(h, a.secret) io.WriteString(h, c) return string(h.Sum(nil)) } func (a *auth) isReady(c chan struct{}) bool { select { case <-a.fail: return false case <-c: return true } } func (a *auth) Send(rq, wq queue.Q) error { e := netstring.NewEncoder(wq.Writer()) if err := a.generateChallenge(); err != nil { return err } e.Encode(a.challenge.self) if !a.isReady(a.recvChallenge) { return nil } if a.challenge.self == a.challenge.peer { return errDupChallenge } e.Encode(a.getHash(a.challenge.peer)) if !a.isReady(a.recvHash) { return nil } if a.hash != a.getHash(a.challenge.self) { close(a.fail) return errAuthFail } close(a.ok) return queue.Copy(rq, wq) } func (a *auth) Recv(rq, wq queue.Q) (err error) { r := rq.Reader() d := netstring.NewDecoder(r) if a.challenge.peer, err = d.Decode(); err != nil { close(a.fail) return } close(a.recvChallenge) if a.hash, err = d.Decode(); err != nil { close(a.fail) return err } close(a.recvHash) if !a.isReady(a.ok) { return nil } return queue.IoCopy(r, wq.Writer()) } func (authHook) Open(env env.Env) (interface{}, error) { a := &auth{ secret: getHookVar(env, "secret"), recvChallenge: make(chan struct{}), recvHash: make(chan struct{}), fail: make(chan struct{}), ok: make(chan struct{}), } return a, nil } func newAuthHook(opts.Opts, env.Env) (hook, error) { return authHook{}, nil } func init() { register("auth", newAuthHook) }