package sftpserver import ( "errors" "fmt" "io" "io/ioutil" "log" "net" "github.com/pkg/sftp" "golang.org/x/crypto/ssh" ) type Instance struct { } //Create a new SFTP Server //listeningIP in the format of 0.0.0.0:2022 func NewSFTPServer(listeningIp string, keyfile string, readOnly bool, passwordCheckFunc func(string, string) bool) (*Instance, error) { // An SSH server is represented by a ServerConfig, which holds // certificate details and handles authentication of ServerConns. config := &ssh.ServerConfig{ PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { // Should use constant-time compare (or better, salt+hash) in // a production setting. fmt.Printf("Login: %s\n", c.User()) if passwordCheckFunc(c.User(), string(pass)) { return nil, nil } return nil, errors.New("password rejected for " + c.User()) }, } privateBytes, err := ioutil.ReadFile(keyfile) if err != nil { return nil, err } private, err := ssh.ParsePrivateKey(privateBytes) if err != nil { return nil, err } config.AddHostKey(private) // Once a ServerConfig has been configured, connections can be // accepted. listener, err := net.Listen("tcp", "0.0.0.0:2022") if err != nil { return nil, err } fmt.Printf("Listening on %v\n", listener.Addr()) for { nConn, err := listener.Accept() if err != nil { return nil, err } go func(nConn net.Conn) error { // Before use, a handshake must be performed on the incoming // net.Conn. _, chans, reqs, err := ssh.NewServerConn(nConn, config) if err != nil { return err } fmt.Println("SSH server established\n") // The incoming Request channel must be serviced. go ssh.DiscardRequests(reqs) // Service the incoming Channel channel. for newChannel := range chans { // Channels have a type, depending on the application level // protocol intended. In the case of an SFTP session, this is "subsystem" // with a payload string of "sftp" fmt.Println("Incoming channel: %s\n", newChannel.ChannelType()) if newChannel.ChannelType() != "session" { newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") fmt.Println("Unknown channel type: %s\n", newChannel.ChannelType()) continue } channel, requests, err := newChannel.Accept() if err != nil { return err } fmt.Println("Channel accepted\n") // Sessions have out-of-band requests such as "shell", // "pty-req" and "env". Here we handle only the // "subsystem" request. go func(in <-chan *ssh.Request) { for req := range in { fmt.Println("Request: %v\n", req.Type) ok := false switch req.Type { case "subsystem": fmt.Println("Subsystem: %s\n", req.Payload[4:]) if string(req.Payload[4:]) == "sftp" { ok = true } } fmt.Println(" - accepted: %v\n", ok) req.Reply(ok, nil) } }(requests) serverOptions := []sftp.ServerOption{} if readOnly { serverOptions = append(serverOptions, sftp.ReadOnly()) fmt.Println("Read-only server\n") } else { fmt.Println("Read write server\n") } server, err := sftp.NewServer( channel, serverOptions..., ) if err != nil { return err } go func() { if err := server.Serve(); err == io.EOF { server.Close() log.Print("sftp client exited session.") } else if err != nil { log.Fatal("sftp server completed with error:", err) } }() } log.Println("Connection cycle ended") return nil }(nConn) } return &Instance{}, nil }