-
-
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
| package main | |
| import ( | |
| "fmt" | |
| "io" | |
| "io/ioutil" | |
| "log" | |
| "net" | |
| "os/exec" | |
| "sync" | |
| "github.com/kr/pty" | |
| "golang.org/x/crypto/ssh" | |
| "golang.org/x/crypto/ssh/terminal" | |
| ) | |
| 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("allocating terminal...") | |
| //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 | |
| } | |
| //this might be wrong? | |
| fd := int(f.Fd()) | |
| //set raw mode | |
| prev, err := terminal.MakeRaw(fd) | |
| if err != nil { | |
| log.Printf("could not set raw mode (%s)", err) | |
| continue | |
| } | |
| var once sync.Once | |
| close := func() { | |
| terminal.Restore(fd, prev) | |
| 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 | |
| log.Printf("accepting shell request...") | |
| } | |
| } | |
| if !ok { | |
| log.Printf("declining %s request...", req.Type) | |
| } | |
| req.Reply(ok, nil) | |
| } | |
| }(requests) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment