mirror of https://github.com/pkg/sftp.git
Created working example SFTP server
Purely gluing together the standalone_server and the SSH server examples, since it took me a while to work out how they fit together. Thought it may save the next person a few minutes!
This commit is contained in:
parent
e09e01e6e1
commit
9287f24175
|
|
@ -3,3 +3,6 @@
|
||||||
|
|
||||||
server_standalone/server_standalone
|
server_standalone/server_standalone
|
||||||
|
|
||||||
|
examples/sftp-server/id_rsa
|
||||||
|
examples/sftp-server/id_rsa.pub
|
||||||
|
examples/sftp-server/sftp-server
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
Example SFTP server implementation
|
||||||
|
===
|
||||||
|
|
||||||
|
In order to use this example you will need an RSA key.
|
||||||
|
|
||||||
|
On linux-like systems with openssh installed, you can use the command:
|
||||||
|
|
||||||
|
```
|
||||||
|
ssh-keygen -t rsa -f id_rsa
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you will be able to run the sftp-server command in the current directory.
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
// 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/ioutil"
|
||||||
|
"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() {
|
||||||
|
|
||||||
|
readOnly := false
|
||||||
|
debugLevelStr := "none"
|
||||||
|
debugLevel := 0
|
||||||
|
debugStderr := false
|
||||||
|
rootDir := ""
|
||||||
|
|
||||||
|
flag.BoolVar(&readOnly, "R", false, "read-only server")
|
||||||
|
flag.BoolVar(&debugStderr, "e", false, "debug to stderr")
|
||||||
|
flag.StringVar(&debugLevelStr, "l", "none", "debug level")
|
||||||
|
flag.StringVar(&debugLevelStr, "root", "", "root directory")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// term := terminal.NewTerminal(channel, "> ")
|
||||||
|
debugStream := ioutil.Discard
|
||||||
|
if debugStderr {
|
||||||
|
debugStream = os.Stderr
|
||||||
|
debugLevel = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
panic("Failed to load private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
private, err := ssh.ParsePrivateKey(privateBytes)
|
||||||
|
if err != nil {
|
||||||
|
panic("Failed to parse private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
panic("failed to listen for connection")
|
||||||
|
}
|
||||||
|
fmt.Printf("Listening on %v\n", listener.Addr())
|
||||||
|
|
||||||
|
nConn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to accept incoming connection")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before use, a handshake must be performed on the incoming
|
||||||
|
// net.Conn.
|
||||||
|
_, chans, reqs, err := ssh.NewServerConn(nConn, config)
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to handshake")
|
||||||
|
}
|
||||||
|
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 a shell, the type is
|
||||||
|
// "session" and ServerShell may be used to present a simple
|
||||||
|
// terminal interface.
|
||||||
|
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 {
|
||||||
|
panic("could not accept channel.")
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
|
||||||
|
server, err := sftp.NewServer(channel, channel, debugStream, debugLevel, readOnly, rootDir)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
go server.Serve()
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue