Created
November 3, 2018 19:19
-
-
Save eikenb/b60438ea2c73cd80f780fc3c13a234ae to your computer and use it in GitHub Desktop.
sftp-server-looping
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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" | |
"sync" | |
"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()) | |
for { | |
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. | |
_, chans, reqs, err := ssh.NewServerConn(nConn, config) | |
if err != nil { | |
log.Fatal("failed to handshake", err) | |
} | |
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. | |
var wg sync.WaitGroup | |
wg.Add(1) | |
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 | |
wg.Done() | |
} | |
} | |
fmt.Fprintf(debugStream, " - accepted: %v\n", ok) | |
req.Reply(ok, nil) | |
} | |
fmt.Println("request chan closed") | |
}(requests) | |
serverOptions := []sftp.ServerOption{ | |
sftp.WithDebug(debugStream), | |
} | |
if readOnly { | |
serverOptions = append(serverOptions, sftp.ReadOnly()) | |
fmt.Fprintf(debugStream, "Read-only server\n") | |
} else { | |
fmt.Fprintf(debugStream, "Read write server\n") | |
} | |
wg.Wait() | |
server, err := sftp.NewServer( | |
channel, | |
serverOptions..., | |
) | |
if err != nil { | |
log.Fatal(err) | |
} | |
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) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment