diff --git a/_examples/ssh-sftp/sftp.go b/_examples/ssh-sftp/sftp.go new file mode 100644 index 0000000..163a015 --- /dev/null +++ b/_examples/ssh-sftp/sftp.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + "github.com/gliderlabs/ssh" + "github.com/pkg/sftp" + "io" + "io/ioutil" + "log" +) + +func SftpHandler(sess ssh.Session) { + debugStream := ioutil.Discard + serverOptions := []sftp.ServerOption{ + sftp.WithDebug(debugStream), + } + server, err := sftp.NewServer( + sess, + serverOptions..., + ) + if err != nil { + log.Printf("sftp server init error: %s\n", err) + return + } + if err := server.Serve(); err == io.EOF { + server.Close() + fmt.Println("sftp client exited session.") + } else if err != nil { + fmt.Println("sftp server completed with error:", err) + } + +} + +func main() { + srv := ssh.Server{ + Addr: ":2223", + SubsystemHandlers: map[string]ssh.SubsystemHandler{}, + } + srv.SetSubsystemHandler("sftp", SftpHandler) + log.Fatal(srv.ListenAndServe()) +} diff --git a/server.go b/server.go index 50eb632..3b6cd20 100644 --- a/server.go +++ b/server.go @@ -40,6 +40,8 @@ type Server struct { channelHandlers map[string]channelHandler requestHandlers map[string]RequestHandler + SubsystemHandlers map[string]SubsystemHandler + listenerWg sync.WaitGroup mu sync.Mutex listeners map[net.Listener]struct{} @@ -381,3 +383,7 @@ func (srv *Server) trackConn(c *gossh.ServerConn, add bool) { srv.connWg.Done() } } + +func (srv *Server) SetSubsystemHandler(name string, handler SubsystemHandler) { + srv.SubsystemHandlers[name] = handler +} diff --git a/session.go b/session.go index 19ddda6..86a69ce 100644 --- a/session.go +++ b/session.go @@ -1,7 +1,6 @@ package ssh import ( - "bytes" "context" "errors" "fmt" @@ -90,6 +89,8 @@ func sessionHandler(srv *Server, conn *gossh.ServerConn, newChan gossh.NewChanne ptyCb: srv.PtyCallback, sessReqCb: srv.SessionRequestCallback, ctx: ctx, + + subsystemHandlers: srv.SubsystemHandlers, } sess.handleRequests(reqs) } @@ -110,21 +111,24 @@ type session struct { ctx Context sigCh chan<- Signal sigBuf []Signal + + subsystemHandlers map[string]SubsystemHandler } func (sess *session) Write(p []byte) (n int, err error) { - if sess.pty != nil { - m := len(p) - // normalize \n to \r\n when pty is accepted. - // this is a hardcoded shortcut since we don't support terminal modes. - p = bytes.Replace(p, []byte{'\n'}, []byte{'\r', '\n'}, -1) - p = bytes.Replace(p, []byte{'\r', '\r', '\n'}, []byte{'\r', '\n'}, -1) - n, err = sess.Channel.Write(p) - if n > m { - n = m - } - return - } + // If change the \n to \r\n, then zmodem(rzsz) will be error + //if sess.pty != nil { + // m := len(p) + // // normalize \n to \r\n when pty is accepted. + // // this is a hardcoded shortcut since we don't support terminal modes. + // p = bytes.Replace(p, []byte{'\n'}, []byte{'\r', '\n'}, -1) + // p = bytes.Replace(p, []byte{'\r', '\r', '\n'}, []byte{'\r', '\n'}, -1) + // n, err = sess.Channel.Write(p) + // if n > m { + // n = m + // } + // return + //} return sess.Channel.Write(p) } @@ -292,6 +296,21 @@ func (sess *session) handleRequests(reqs <-chan *gossh.Request) { // TODO: option/callback to allow agent forwarding SetAgentRequested(sess.ctx) req.Reply(true, nil) + case "subsystem": + subname := string(req.Payload[4:]) + handler, ok := sess.subsystemHandlers[subname] + if !ok { + req.Reply(false, nil) + continue + } + sess.handled = true + req.Reply(true, nil) + + go func() { + handler(sess) + sess.Exit(0) + }() + default: // TODO: debug log req.Reply(false, nil) diff --git a/ssh.go b/ssh.go index cb0d77f..aefb681 100644 --- a/ssh.go +++ b/ssh.go @@ -35,6 +35,9 @@ type Option func(*Server) error // Handler is a callback for handling established SSH sessions. type Handler func(Session) +// SubsystemHandler is a callback for handling session subsystem request +type SubsystemHandler func(Session) + // PublicKeyHandler is a callback for performing public key authentication. type PublicKeyHandler func(ctx Context, key PublicKey) bool