| // An example SFTP server implementation using the golang SSH package. |
| // Serves the whole filesystem visible to the user, and has a hard-coded username and password, |
| // so not for real use! |
| package main |
| |
| import ( |
| "flag" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "log" |
| "net" |
| "os" |
| |
| "github.com/pkg/sftp" |
| "golang.org/x/crypto/ssh" |
| ) |
| |
| // Based on example server code from golang.org/x/crypto/ssh and server_standalone |
| func main() { |
| |
| var ( |
| readOnly bool |
| debugStderr bool |
| ) |
| |
| flag.BoolVar(&readOnly, "R", false, "read-only server") |
| flag.BoolVar(&debugStderr, "e", false, "debug to stderr") |
| flag.Parse() |
| |
| debugStream := ioutil.Discard |
| if debugStderr { |
| debugStream = os.Stderr |
| } |
| |
| // 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.Fprintf(debugStream, "Login: %s\n", c.User()) |
| if c.User() == "testuser" && string(pass) == "tiger" { |
| return nil, nil |
| } |
| return nil, fmt.Errorf("password rejected for %q", c.User()) |
| }, |
| } |
| |
| privateBytes, err := ioutil.ReadFile("id_rsa") |
| if err != nil { |
| log.Fatal("Failed to load private key", err) |
| } |
| |
| private, err := ssh.ParsePrivateKey(privateBytes) |
| if err != nil { |
| log.Fatal("Failed to parse private key", 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 { |
| log.Fatal("failed to listen for connection", err) |
| } |
| fmt.Printf("Listening on %v\n", listener.Addr()) |
| |
| nConn, err := listener.Accept() |
| if err != nil { |
| log.Fatal("failed to accept incoming connection", err) |
| } |
| |
| // Before use, a handshake must be performed on the incoming net.Conn. |
| sconn, chans, reqs, err := ssh.NewServerConn(nConn, config) |
| if err != nil { |
| log.Fatal("failed to handshake", err) |
| } |
| log.Println("login detected:", sconn.User()) |
| fmt.Fprintf(debugStream, "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 "<length=4>sftp" |
| fmt.Fprintf(debugStream, "Incoming channel: %s\n", newChannel.ChannelType()) |
| if newChannel.ChannelType() != "session" { |
| newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") |
| fmt.Fprintf(debugStream, "Unknown channel type: %s\n", newChannel.ChannelType()) |
| continue |
| } |
| channel, requests, err := newChannel.Accept() |
| if err != nil { |
| log.Fatal("could not accept channel.", err) |
| } |
| fmt.Fprintf(debugStream, "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.Fprintf(debugStream, "Request: %v\n", req.Type) |
| ok := false |
| switch req.Type { |
| case "subsystem": |
| fmt.Fprintf(debugStream, "Subsystem: %s\n", req.Payload[4:]) |
| if string(req.Payload[4:]) == "sftp" { |
| ok = true |
| } |
| } |
| fmt.Fprintf(debugStream, " - accepted: %v\n", ok) |
| req.Reply(ok, nil) |
| } |
| }(requests) |
| |
| root := sftp.InMemHandler() |
| server := sftp.NewRequestServer(channel, root) |
| 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) |
| } |
| } |
| } |