-
-
Save hartsock/17f623a1afde48ad5483fa7f491b45e8 to your computer and use it in GitHub Desktop.
Go SSH server complete example - Read more here https://blog.gopheracademy.com/go-and-ssh/
This file contains hidden or 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
| // A simple SSH server providing bash sessions | |
| // | |
| // Requires: | |
| // go get github.com/kr/pty | |
| // go get golang.org/x/crypto/ssh | |
| // | |
| // Server: | |
| // ssh-keygen -t rsa #generate server keypair | |
| // go run sshd.go | |
| // | |
| // Client: | |
| // ssh foo@localhost -p 2022 #pass=bar | |
| package main | |
| import ( | |
| "encoding/binary" | |
| "fmt" | |
| "io" | |
| "io/ioutil" | |
| "log" | |
| "net" | |
| "os/exec" | |
| "sync" | |
| "syscall" | |
| "unsafe" | |
| "github.com/kr/pty" | |
| "golang.org/x/crypto/ssh" | |
| ) | |
| func main() { | |
| // 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. | |
| if c.User() == "foo" && string(pass) == "bar" { | |
| return nil, nil | |
| } | |
| return nil, fmt.Errorf("password rejected for %q", c.User()) | |
| }, | |
| } | |
| // You can generate a keypair with 'ssh-keygen -t rsa -C "[email protected]"' | |
| privateBytes, err := ioutil.ReadFile("id_rsa") | |
| if err != nil { | |
| log.Fatal("Failed to load private key (./id_rsa)") | |
| } | |
| private, err := ssh.ParsePrivateKey(privateBytes) | |
| if err != nil { | |
| log.Fatal("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 { | |
| log.Fatal("failed to listen on 2022") | |
| } | |
| // Accept all connections | |
| log.Print("listening on 2022...") | |
| for { | |
| tcpConn, err := listener.Accept() | |
| if err != nil { | |
| log.Printf("failed to accept incoming connection (%s)", err) | |
| continue | |
| } | |
| // Before use, a handshake must be performed on the incoming net.Conn. | |
| sshConn, chans, reqs, err := ssh.NewServerConn(tcpConn, config) | |
| if err != nil { | |
| log.Printf("failed to handshake (%s)", err) | |
| continue | |
| } | |
| log.Printf("new ssh connection from %s (%s)", sshConn.RemoteAddr(), sshConn.ClientVersion()) | |
| // Print incoming [out-of-band] Requests | |
| go handleRequests(reqs) | |
| // Accept all channels | |
| go handleChannels(chans) | |
| } | |
| } | |
| func handleRequests(reqs <-chan *ssh.Request) { | |
| for req := range reqs { | |
| log.Printf("recieved request: %+v", req) | |
| } | |
| } | |
| func handleChannels(chans <-chan ssh.NewChannel) { | |
| // 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. | |
| if t := newChannel.ChannelType(); t != "session" { | |
| newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t)) | |
| continue | |
| } | |
| channel, requests, err := newChannel.Accept() | |
| if err != nil { | |
| log.Printf("could not accept channel (%s)", err) | |
| continue | |
| } | |
| // allocate a terminal for this channel | |
| log.Print("creating pty...") | |
| //fire up bash for this session | |
| c := exec.Command("bash") | |
| f, err := pty.Start(c) | |
| if err != nil { | |
| log.Printf("could not start pty (%s)", err) | |
| continue | |
| } | |
| //teardown session | |
| var once sync.Once | |
| close := func() { | |
| channel.Close() | |
| log.Printf("session closed") | |
| } | |
| //link session and bash streams | |
| go func() { | |
| io.Copy(channel, f) | |
| once.Do(close) | |
| }() | |
| go func() { | |
| io.Copy(f, channel) | |
| once.Do(close) | |
| }() | |
| // Sessions have out-of-band requests such as "shell", | |
| // "pty-req" and "env". Here we handle only the | |
| // "shell" request. | |
| go func(in <-chan *ssh.Request) { | |
| for req := range in { | |
| ok := false | |
| switch req.Type { | |
| case "shell": | |
| // We don't accept any commands (Payload), | |
| // only the default shell. | |
| if len(req.Payload) == 0 { | |
| ok = true | |
| } | |
| case "pty-req": | |
| // Responding 'ok' here will let the client | |
| // know we have a pty ready for input | |
| ok = true | |
| // Parse body... | |
| termLen := req.Payload[3] | |
| termEnv := string(req.Payload[4 : termLen+4]) | |
| w, h := parseDims(req.Payload[termLen+4:]) | |
| SetWinsize(f.Fd(), w, h) | |
| log.Printf("pty-req '%s'", termEnv) | |
| case "window-change": | |
| w, h := parseDims(req.Payload) | |
| SetWinsize(f.Fd(), w, h) | |
| continue //no response | |
| } | |
| if !ok { | |
| log.Printf("declining %s request...", req.Type) | |
| } | |
| req.Reply(ok, nil) | |
| } | |
| }(requests) | |
| } | |
| } | |
| // ======================= | |
| // Window size helpers | |
| func parseDims(b []byte) (uint32, uint32) { | |
| w := binary.BigEndian.Uint32(b) | |
| h := binary.BigEndian.Uint32(b[4:]) | |
| return w, h | |
| } | |
| // Winsize stores the Height and Width of a terminal. | |
| type Winsize struct { | |
| Height uint16 | |
| Width uint16 | |
| x uint16 // unused | |
| y uint16 // unused | |
| } | |
| // SetWinsize sets the size of the given pty. | |
| func SetWinsize(fd uintptr, w, h uint32) { | |
| log.Printf("window resize %dx%d", w, h) | |
| ws := &Winsize{Width: uint16(w), Height: uint16(h)} | |
| syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws))) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment