From cb7cc4aaecff5020860f45a5b1f671d7e33833fd Mon Sep 17 00:00:00 2001 From: "rui.zheng" Date: Thu, 27 Jul 2017 00:16:20 +0800 Subject: [PATCH] add ssh server --- gost/cli/cli.go | 12 ++- gost/kcp.go | 16 ++- gost/ssh.go | 278 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 297 insertions(+), 9 deletions(-) diff --git a/gost/cli/cli.go b/gost/cli/cli.go index 0ceda4a..f366fbc 100644 --- a/gost/cli/cli.go +++ b/gost/cli/cli.go @@ -154,20 +154,24 @@ func request(chain *gost.Chain, start <-chan struct{}) { conn, err := chain.Dial("localhost:10000") if err != nil { - log.Fatal(err) + log.Println(err) + return } defer conn.Close() //conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true}) req, err := http.NewRequest(http.MethodGet, "http://localhost:10000/pkg", nil) if err != nil { - log.Fatal(err) + log.Println(err) + return } if err := req.Write(conn); err != nil { - log.Fatal(err) + log.Println(err) + return } resp, err := http.ReadResponse(bufio.NewReader(conn), req) if err != nil { - log.Fatal(err) + log.Println(err) + return } defer resp.Body.Close() diff --git a/gost/kcp.go b/gost/kcp.go index 08c5684..585d093 100644 --- a/gost/kcp.go +++ b/gost/kcp.go @@ -320,19 +320,21 @@ func KCPListener(addr string, config *KCPConfig) (Listener, error) { config: config, ln: ln, connChan: make(chan net.Conn, 1024), - errChan: make(chan error), + errChan: make(chan error, 1), } - go l.acceptLoop() + go l.listenLoop() return l, nil } -func (l *kcpListener) acceptLoop() { +func (l *kcpListener) listenLoop() { for { conn, err := l.ln.AcceptKCP() if err != nil { log.Log("[kcp] accept:", err) - continue + l.errChan <- err + close(l.errChan) + return } conn.SetStreamMode(true) conn.SetNoDelay(l.config.NoDelay, l.config.Interval, l.config.Resend, l.config.NoCongestion) @@ -345,9 +347,13 @@ func (l *kcpListener) acceptLoop() { } func (l *kcpListener) Accept() (conn net.Conn, err error) { + var ok bool select { case conn = <-l.connChan: - case err = <-l.errChan: + case err, ok = <-l.errChan: + if !ok { + err = errors.New("accpet on closed listener") + } } return } diff --git a/gost/ssh.go b/gost/ssh.go index 71be68f..d4930d2 100644 --- a/gost/ssh.go +++ b/gost/ssh.go @@ -1 +1,279 @@ package gost + +import ( + "errors" + "fmt" + "net" + "net/url" + "strconv" + + "github.com/go-log/log" + "golang.org/x/crypto/ssh" +) + +// Applicaple SSH Request types for Port Forwarding - RFC 4254 7.X +const ( + DirectForwardRequest = "direct-tcpip" // RFC 4254 7.2 + RemoteForwardRequest = "tcpip-forward" // RFC 4254 7.1 + ForwardedTCPReturnRequest = "forwarded-tcpip" // RFC 4254 7.2 + CancelRemoteForwardRequest = "cancel-tcpip-forward" // RFC 4254 7.1 +) + +type sshListener struct { + net.Listener + config *ssh.ServerConfig + connChan chan net.Conn + errChan chan error +} + +func SSHListener(addr string, config *ssh.ServerConfig) (Listener, error) { + ln, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + + if config == nil { + config = &ssh.ServerConfig{} + } + + l := &sshListener{ + Listener: ln, + config: config, + connChan: make(chan net.Conn, 1024), + errChan: make(chan error, 1), + } + + go l.listenLoop() + + return l, nil +} + +func (l *sshListener) listenLoop() { + for { + conn, err := l.Listener.Accept() + if err != nil { + log.Log("[ssh] accept:", err) + l.errChan <- err + close(l.errChan) + return + } + go l.serveConn(conn) + } +} + +func (l *sshListener) serveConn(conn net.Conn) { + sshConn, chans, reqs, err := ssh.NewServerConn(conn, l.config) + if err != nil { + log.Logf("[ssh] %s -> %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err) + conn.Close() + return + } + defer sshConn.Close() + + quit := make(chan interface{}) + go func() { + for req := range reqs { + switch req.Type { + case RemoteForwardRequest: + // go l.tcpipForwardRequest(conn, req, quit) + default: + log.Log("[ssh] unknown channel type:", req.Type) + if req.WantReply { + req.Reply(false, nil) + } + } + } + }() + + go func() { + for newChannel := range chans { + // Check the type of channel + t := newChannel.ChannelType() + switch t { + case DirectForwardRequest: + /* + channel, requests, err := newChannel.Accept() + if err != nil { + log.Log("[ssh] Could not accept channel:", err) + continue + } + p := directForward{} + ssh.Unmarshal(newChannel.ExtraData(), &p) + + if p.Host1 == "" { + p.Host1 = "" + } + + go ssh.DiscardRequests(requests) + go l.directPortForwardChannel(channel, fmt.Sprintf("%s:%d", p.Host1, p.Port1)) + */ + default: + log.Log("[ssh] Unknown channel type:", t) + newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t)) + } + } + }() + + sshConn.Wait() + close(quit) + +} + +func (l *sshListener) Accept() (conn net.Conn, err error) { + var ok bool + select { + case conn = <-l.connChan: + case err, ok = <-l.errChan: + if !ok { + err = errors.New("accpet on closed listener") + } + } + return +} + +// directForward is structure for RFC 4254 7.2 - can be used for "forwarded-tcpip" and "direct-tcpip" +type directForward struct { + Host1 string + Port1 uint32 + Host2 string + Port2 uint32 +} + +func (p directForward) String() string { + return fmt.Sprintf("%s:%d -> %s:%d", p.Host2, p.Port2, p.Host1, p.Port1) +} + +/* +func (l *sshListener) directPortForwardChannel(channel ssh.Channel, raddr string) { + defer channel.Close() + + log.Logf("[ssh-tcp] %s - %s", l.Addr, raddr) + + //! if !s.Base.Node.Can("tcp", raddr) { + //! glog.Errorf("Unauthorized to tcp connect to %s", raddr) + //! return + //! } + + conn, err := h.Base.Chain.Dial(raddr) + if err != nil { + glog.V(LINFO).Infof("[ssh-tcp] %s - %s : %s", s.Addr, raddr, err) + return + } + defer conn.Close() + + glog.V(LINFO).Infof("[ssh-tcp] %s <-> %s", s.Addr, raddr) + Transport(conn, channel) + glog.V(LINFO).Infof("[ssh-tcp] %s >-< %s", s.Addr, raddr) +} + +// tcpipForward is structure for RFC 4254 7.1 "tcpip-forward" request +type tcpipForward struct { + Host string + Port uint32 +} + +func (s *SSHServer) tcpipForwardRequest(sshConn ssh.Conn, req *ssh.Request, quit <-chan interface{}) { + t := tcpipForward{} + ssh.Unmarshal(req.Payload, &t) + + addr := fmt.Sprintf("%s:%d", t.Host, t.Port) + + if !s.Base.Node.Can("rtcp", addr) { + glog.Errorf("Unauthorized to tcp bind to %s", addr) + req.Reply(false, nil) + return + } + + glog.V(LINFO).Infoln("[ssh-rtcp] listening tcp", addr) + ln, err := net.Listen("tcp", addr) //tie to the client connection + if err != nil { + glog.V(LWARNING).Infoln("[ssh-rtcp]", err) + req.Reply(false, nil) + return + } + defer ln.Close() + + replyFunc := func() error { + if t.Port == 0 && req.WantReply { // Client sent port 0. let them know which port is actually being used + _, port, err := getHostPortFromAddr(ln.Addr()) + if err != nil { + return err + } + var b [4]byte + binary.BigEndian.PutUint32(b[:], uint32(port)) + t.Port = uint32(port) + return req.Reply(true, b[:]) + } + return req.Reply(true, nil) + } + if err := replyFunc(); err != nil { + glog.V(LWARNING).Infoln("[ssh-rtcp]", err) + return + } + + go func() { + for { + conn, err := ln.Accept() + if err != nil { // Unable to accept new connection - listener likely closed + return + } + + go func(conn net.Conn) { + defer conn.Close() + + p := directForward{} + var err error + + var portnum int + p.Host1 = t.Host + p.Port1 = t.Port + p.Host2, portnum, err = getHostPortFromAddr(conn.RemoteAddr()) + if err != nil { + return + } + + p.Port2 = uint32(portnum) + glog.V(3).Info(p) + ch, reqs, err := sshConn.OpenChannel(ForwardedTCPReturnRequest, ssh.Marshal(p)) + if err != nil { + glog.V(1).Infoln("[ssh-rtcp] open forwarded channel:", err) + return + } + defer ch.Close() + go ssh.DiscardRequests(reqs) + + glog.V(LINFO).Infof("[ssh-rtcp] %s <-> %s", conn.RemoteAddr(), conn.LocalAddr()) + Transport(ch, conn) + glog.V(LINFO).Infof("[ssh-rtcp] %s >-< %s", conn.RemoteAddr(), conn.LocalAddr()) + }(conn) + } + }() + + <-quit +} +*/ + +func getHostPortFromAddr(addr net.Addr) (host string, port int, err error) { + host, portString, err := net.SplitHostPort(addr.String()) + if err != nil { + return + } + port, err = strconv.Atoi(portString) + return +} + +type PasswordCallbackFunc func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) + +func SSHPasswordCallback(users []*url.Userinfo) PasswordCallbackFunc { + return func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) { + for _, user := range users { + u := user.Username() + p, _ := user.Password() + if u == conn.User() && p == string(password) { + return nil, nil + } + } + log.Logf("[ssh] %s -> %s : password rejected for %s", conn.RemoteAddr(), conn.LocalAddr(), conn.User()) + return nil, fmt.Errorf("password rejected for %s", conn.User()) + } +}