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