Skip to content

Instantly share code, notes, and snippets.

@jpillora
Last active August 13, 2025 02:26
Show Gist options
  • Select an option

  • Save jpillora/b480fde82bff51a06238 to your computer and use it in GitHub Desktop.

Select an option

Save jpillora/b480fde82bff51a06238 to your computer and use it in GitHub Desktop.

Revisions

  1. jpillora revised this gist Jun 22, 2016. No changes.
  2. jpillora revised this gist Jun 22, 2016. No changes.
  3. jpillora revised this gist Jan 29, 2015. 1 changed file with 36 additions and 45 deletions.
    81 changes: 36 additions & 45 deletions sshd.go
    Original file line number Diff line number Diff line change
    @@ -85,57 +85,51 @@ func main() {

    log.Printf("New SSH connection from %s (%s)", sshConn.RemoteAddr(), sshConn.ClientVersion())
    // Discard all global out-of-band Requests
    go handleRequests(reqs)
    go ssh.DiscardRequests(reqs)
    // Accept all channels
    go handleChannels(chans)
    }
    }

    func handleRequests(requests <-chan *ssh.Request) {
    for req := range requests {
    log.Printf("Recieved out-of-band request: %+v", req)
    }
    }

    func handleChannels(chans <-chan ssh.NewChannel) {
    // Service the incoming Channel channel.
    // Service the incoming Channel channel in go routine
    for newChannel := range chans {
    // Since we're handling the execution of a shell, we expect a
    // channel type of "session". However, there are also: "x11", "direct-tcpip"
    // and "forwarded-tcpip" channel types.
    if t := newChannel.ChannelType(); t != "session" {
    newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
    continue
    }

    // At this point, we have the opportunity to reject the client's
    // request for another logical connection
    channel, requests, err := newChannel.Accept()
    if err != nil {
    log.Printf("Could not accept channel (%s)", err)
    continue
    }

    handleChannel(channel, requests)
    go handleChannel(newChannel)
    }
    }

    func handleChannel(channel ssh.Channel, requests <-chan *ssh.Request) {
    func handleChannel(newChannel ssh.NewChannel) {
    // Since we're handling a shell, we expect a
    // channel type of "session". The also describes
    // "x11", "direct-tcpip" and "forwarded-tcpip"
    // channel types.
    if t := newChannel.ChannelType(); t != "session" {
    newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
    return
    }

    // fire up bash for this session
    // At this point, we have the opportunity to reject the client's
    // request for another logical connection
    connection, requests, err := newChannel.Accept()
    if err != nil {
    log.Printf("Could not accept channel (%s)", err)
    return
    }

    // Fire up bash for this session
    bash := exec.Command("bash")

    // prepare teardown function
    // Prepare teardown function
    close := func() {
    channel.Close()
    connection.Close()
    _, err := bash.Process.Wait()
    if err != nil {
    log.Printf("Failed to exit bash (%s)", err)
    }
    log.Printf("Session closed")
    }

    // allocate a terminal for this channel
    // Allocate a terminal for this channel
    log.Print("Creating pty...")
    bashf, err := pty.Start(bash)
    if err != nil {
    @@ -144,6 +138,17 @@ func handleChannel(channel ssh.Channel, requests <-chan *ssh.Request) {
    return
    }

    //pipe session to bash and visa-versa
    var once sync.Once
    go func() {
    io.Copy(connection, bashf)
    once.Do(close)
    }()
    go func() {
    io.Copy(bashf, connection)
    once.Do(close)
    }()

    // Sessions have out-of-band requests such as "shell", "pty-req" and "env"
    go func() {
    for req := range requests {
    @@ -165,22 +170,8 @@ func handleChannel(channel ssh.Channel, requests <-chan *ssh.Request) {
    w, h := parseDims(req.Payload)
    SetWinsize(bashf.Fd(), w, h)
    }

    }
    }()

    //pipe session to bash and visa-versa
    var once sync.Once
    go func() {
    io.Copy(channel, bashf)
    once.Do(close)
    }()
    go func() {
    io.Copy(bashf, channel)
    once.Do(close)
    }()

    //*important* - does not block
    }

    // =======================
    @@ -208,4 +199,4 @@ func SetWinsize(fd uintptr, w, h uint32) {
    syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws)))
    }

    // Borrowed from https://github.com/creack/termios/blob/master/win/win.go
    // Borrowed from https://github.com/creack/termios/blob/master/win/win.go
  4. jpillora revised this gist Dec 26, 2014. 1 changed file with 71 additions and 69 deletions.
    140 changes: 71 additions & 69 deletions sshd.go
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    // A simple SSH server providing bash sessions
    // A small SSH daemon providing bash sessions
    //
    // Server:
    // cd my/new/dir/
    @@ -8,7 +8,7 @@
    // go run sshd.go
    //
    // Client:
    // ssh foo@localhost -p 2022 #pass=bar
    // ssh foo@localhost -p 2200 #pass=bar

    package main

    @@ -44,7 +44,8 @@ func main() {
    }
    return nil, fmt.Errorf("password rejected for %q", c.User())
    },
    // You may also explicitly allow anonymous client authentication
    // You may also explicitly allow anonymous client authentication, though anon bash
    // sessions may not be a wise idea
    // NoClientAuth: true,
    }

    @@ -62,13 +63,13 @@ func main() {
    config.AddHostKey(private)

    // Once a ServerConfig has been configured, connections can be accepted.
    listener, err := net.Listen("tcp", "0.0.0.0:2022")
    listener, err := net.Listen("tcp", "0.0.0.0:2200")
    if err != nil {
    log.Fatal("Failed to listen on 2022")
    log.Fatalf("Failed to listen on 2200 (%s)", err)
    }

    // Accept all connections
    log.Print("Listening on 2022...")
    log.Print("Listening on 2200...")
    for {
    tcpConn, err := listener.Accept()
    if err != nil {
    @@ -90,9 +91,9 @@ func main() {
    }
    }

    func handleRequests(reqs <-chan *ssh.Request) {
    for req := range reqs {
    log.Printf("recieved out-of-band request: %+v", req)
    func handleRequests(requests <-chan *ssh.Request) {
    for req := range requests {
    log.Printf("Recieved out-of-band request: %+v", req)
    }
    }

    @@ -111,73 +112,75 @@ func handleChannels(chans <-chan ssh.NewChannel) {
    // request for another logical connection
    channel, requests, err := newChannel.Accept()
    if err != nil {
    log.Printf("could not accept channel (%s)", err)
    log.Printf("Could not accept channel (%s)", err)
    continue
    }
    // fire up bash for this session
    bash := exec.Command("bash")
    // allocate a terminal for this channel
    log.Print("creating pty...")
    bashf, err := pty.Start(bash)

    handleChannel(channel, requests)
    }
    }

    func handleChannel(channel ssh.Channel, requests <-chan *ssh.Request) {

    // fire up bash for this session
    bash := exec.Command("bash")

    // prepare teardown function
    close := func() {
    channel.Close()
    _, err := bash.Process.Wait()
    if err != nil {
    log.Printf("could not start pty (%s)", err)
    continue
    log.Printf("Failed to exit bash (%s)", err)
    }
    log.Printf("Session closed")
    }

    //teardown session
    var once sync.Once
    close := func() {
    channel.Close()
    _, err := bash.Process.Wait()
    if err != nil {
    log.Printf("failed to exit bash (%s)", err)
    }
    log.Printf("session closed")
    }
    // allocate a terminal for this channel
    log.Print("Creating pty...")
    bashf, err := pty.Start(bash)
    if err != nil {
    log.Printf("Could not start pty (%s)", err)
    close()
    return
    }

    //pipe session to bash and visa-versa
    go func() {
    io.Copy(channel, bashf)
    once.Do(close)
    }()
    go func() {
    io.Copy(bashf, channel)
    once.Do(close)
    }()

    // Sessions have out-of-band requests such as "shell", "pty-req" and "env"
    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(bashf.Fd(), w, h)
    log.Printf("pty-req '%s'", termEnv)
    case "window-change":
    w, h := parseDims(req.Payload)
    SetWinsize(bashf.Fd(), w, h)
    continue //no response
    // Sessions have out-of-band requests such as "shell", "pty-req" and "env"
    go func() {
    for req := range requests {
    switch req.Type {
    case "shell":
    // We only accept the default shell
    // (i.e. no command in the Payload)
    if len(req.Payload) == 0 {
    req.Reply(true, nil)
    }
    if !ok {
    log.Printf("declining %s request...", req.Type)
    }
    req.Reply(ok, nil)
    case "pty-req":
    termLen := req.Payload[3]
    w, h := parseDims(req.Payload[termLen+4:])
    SetWinsize(bashf.Fd(), w, h)
    // Responding true (OK) here will let the client
    // know we have a pty ready for input
    req.Reply(true, nil)
    case "window-change":
    w, h := parseDims(req.Payload)
    SetWinsize(bashf.Fd(), w, h)
    }
    }(requests)
    }

    }
    }()

    //pipe session to bash and visa-versa
    var once sync.Once
    go func() {
    io.Copy(channel, bashf)
    once.Do(close)
    }()
    go func() {
    io.Copy(bashf, channel)
    once.Do(close)
    }()

    //*important* - does not block
    }

    // =======================
    @@ -201,7 +204,6 @@ type Winsize struct {

    // 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)))
    }
  5. jpillora revised this gist Dec 23, 2014. 1 changed file with 36 additions and 26 deletions.
    62 changes: 36 additions & 26 deletions sshd.go
    Original file line number Diff line number Diff line change
    @@ -2,7 +2,8 @@
    //
    // Server:
    // cd my/new/dir/
    // ssh-keygen -t rsa #generate server keypair
    // #generate server keypair
    // ssh-keygen -t rsa
    // go get -v .
    // go run sshd.go
    //
    @@ -29,20 +30,25 @@ import (

    func main() {

    // An SSH server is represented by a ServerConfig, which holds
    // certificate details and handles authentication of ServerConns.
    // In the latest version of crypto/ssh (after Go 1.3), the SSH server type has been removed
    // in favour of an SSH connection type. A ssh.ServerConn is created by passing an existing
    // net.Conn and a ssh.ServerConfig to ssh.NewServerConn, in effect, upgrading the net.Conn
    // into an ssh.ServerConn

    config := &ssh.ServerConfig{
    // NoClientAuth: true,
    //Define a function to run when a client attempts a password login
    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 may also explicitly allow anonymous client authentication
    // NoClientAuth: true,
    }

    // You can generate a keypair with 'ssh-keygen -t rsa -C "[email protected]"'
    // You can generate a keypair with 'ssh-keygen -t rsa'
    privateBytes, err := ioutil.ReadFile("id_rsa")
    if err != nil {
    log.Fatal("Failed to load private key (./id_rsa)")
    @@ -58,26 +64,26 @@ func main() {
    // 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")
    log.Fatal("Failed to listen on 2022")
    }

    // Accept all connections
    log.Print("listening on 2022...")
    log.Print("Listening on 2022...")
    for {
    tcpConn, err := listener.Accept()
    if err != nil {
    log.Printf("failed to accept incoming connection (%s)", err)
    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)
    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
    log.Printf("New SSH connection from %s (%s)", sshConn.RemoteAddr(), sshConn.ClientVersion())
    // Discard all global out-of-band Requests
    go handleRequests(reqs)
    // Accept all channels
    go handleChannels(chans)
    @@ -93,26 +99,26 @@ func handleRequests(reqs <-chan *ssh.Request) {
    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.
    // Since we're handling the execution of a shell, we expect a
    // channel type of "session". However, there are also: "x11", "direct-tcpip"
    // and "forwarded-tcpip" channel types.
    if t := newChannel.ChannelType(); t != "session" {
    newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
    continue
    }

    // At this point, we have the opportunity to reject the client's
    // request for another logical connection
    channel, requests, err := newChannel.Accept()
    if err != nil {
    log.Printf("could not accept channel (%s)", err)
    continue
    }

    // fire up bash for this session
    bash := exec.Command("bash")
    // 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)
    bashf, err := pty.Start(bash)
    if err != nil {
    log.Printf("could not start pty (%s)", err)
    continue
    @@ -122,7 +128,7 @@ func handleChannels(chans <-chan ssh.NewChannel) {
    var once sync.Once
    close := func() {
    channel.Close()
    _, err := c.Process.Wait()
    _, err := bash.Process.Wait()
    if err != nil {
    log.Printf("failed to exit bash (%s)", err)
    }
    @@ -131,11 +137,11 @@ func handleChannels(chans <-chan ssh.NewChannel) {

    //pipe session to bash and visa-versa
    go func() {
    io.Copy(channel, f)
    io.Copy(channel, bashf)
    once.Do(close)
    }()
    go func() {
    io.Copy(f, channel)
    io.Copy(bashf, channel)
    once.Do(close)
    }()

    @@ -158,11 +164,11 @@ func handleChannels(chans <-chan ssh.NewChannel) {
    termLen := req.Payload[3]
    termEnv := string(req.Payload[4 : termLen+4])
    w, h := parseDims(req.Payload[termLen+4:])
    SetWinsize(f.Fd(), w, h)
    SetWinsize(bashf.Fd(), w, h)
    log.Printf("pty-req '%s'", termEnv)
    case "window-change":
    w, h := parseDims(req.Payload)
    SetWinsize(f.Fd(), w, h)
    SetWinsize(bashf.Fd(), w, h)
    continue //no response
    }
    if !ok {
    @@ -176,13 +182,15 @@ func handleChannels(chans <-chan ssh.NewChannel) {

    // =======================

    // parseDims extracts two uint32s from the provided buffer.
    // parseDims extracts terminal dimensions (width x height) from the provided buffer.
    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
    @@ -197,3 +205,5 @@ func SetWinsize(fd uintptr, w, h uint32) {
    ws := &Winsize{Width: uint16(w), Height: uint16(h)}
    syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws)))
    }

    // Borrowed from https://github.com/creack/termios/blob/master/win/win.go
  6. jpillora revised this gist Dec 3, 2014. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions sshd.go
    Original file line number Diff line number Diff line change
    @@ -122,6 +122,10 @@ func handleChannels(chans <-chan ssh.NewChannel) {
    var once sync.Once
    close := func() {
    channel.Close()
    _, err := c.Process.Wait()
    if err != nil {
    log.Printf("failed to exit bash (%s)", err)
    }
    log.Printf("session closed")
    }

  7. jpillora revised this gist Dec 2, 2014. 1 changed file with 5 additions and 4 deletions.
    9 changes: 5 additions & 4 deletions sshd.go
    Original file line number Diff line number Diff line change
    @@ -32,6 +32,7 @@ func main() {
    // An SSH server is represented by a ServerConfig, which holds
    // certificate details and handles authentication of ServerConns.
    config := &ssh.ServerConfig{
    // NoClientAuth: true,
    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" {
    @@ -76,7 +77,7 @@ func main() {
    }

    log.Printf("new ssh connection from %s (%s)", sshConn.RemoteAddr(), sshConn.ClientVersion())
    // Print incoming [out-of-band] Requests
    // Print incoming out-of-band Requests
    go handleRequests(reqs)
    // Accept all channels
    go handleChannels(chans)
    @@ -85,7 +86,7 @@ func main() {

    func handleRequests(reqs <-chan *ssh.Request) {
    for req := range reqs {
    log.Printf("recieved request: %+v", req)
    log.Printf("recieved out-of-band request: %+v", req)
    }
    }

    @@ -124,7 +125,7 @@ func handleChannels(chans <-chan ssh.NewChannel) {
    log.Printf("session closed")
    }

    //link session and bash streams
    //pipe session to bash and visa-versa
    go func() {
    io.Copy(channel, f)
    once.Do(close)
    @@ -170,8 +171,8 @@ func handleChannels(chans <-chan ssh.NewChannel) {
    }

    // =======================
    // Window size helpers

    // parseDims extracts two uint32s from the provided buffer.
    func parseDims(b []byte) (uint32, uint32) {
    w := binary.BigEndian.Uint32(b)
    h := binary.BigEndian.Uint32(b[4:])
  8. jpillora revised this gist Dec 2, 2014. 1 changed file with 3 additions and 8 deletions.
    11 changes: 3 additions & 8 deletions sshd.go
    Original file line number Diff line number Diff line change
    @@ -1,11 +1,9 @@
    // A simple SSH server providing bash sessions
    //
    // Requires:
    // go get github.com/kr/pty
    // go get golang.org/x/crypto/ssh
    //
    // Server:
    // cd my/new/dir/
    // ssh-keygen -t rsa #generate server keypair
    // go get -v .
    // go run sshd.go
    //
    // Client:
    @@ -136,12 +134,9 @@ func handleChannels(chans <-chan ssh.NewChannel) {
    once.Do(close)
    }()

    // Sessions have out-of-band requests such as "shell",
    // "pty-req" and "env". Here we handle only the
    // "shell" request.
    // Sessions have out-of-band requests such as "shell", "pty-req" and "env"
    go func(in <-chan *ssh.Request) {
    for req := range in {

    ok := false
    switch req.Type {
    case "shell":
  9. jpillora revised this gist Dec 2, 2014. 1 changed file with 58 additions and 16 deletions.
    74 changes: 58 additions & 16 deletions sshd.go
    Original file line number Diff line number Diff line change
    @@ -1,17 +1,32 @@
    // 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"
    "golang.org/x/crypto/ssh/terminal"
    )

    func main() {
    @@ -20,8 +35,7 @@ func main() {
    // 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.
    // Should use constant-time compare (or better, salt+hash) in a production setting.
    if c.User() == "foo" && string(pass) == "bar" {
    return nil, nil
    }
    @@ -95,7 +109,7 @@ func handleChannels(chans <-chan ssh.NewChannel) {
    }

    // allocate a terminal for this channel
    log.Print("allocating terminal...")
    log.Print("creating pty...")

    //fire up bash for this session
    c := exec.Command("bash")
    @@ -105,19 +119,9 @@ func handleChannels(chans <-chan ssh.NewChannel) {
    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
    }

    //teardown session
    var once sync.Once
    close := func() {
    terminal.Restore(fd, prev)
    channel.Close()
    log.Printf("session closed")
    }
    @@ -137,15 +141,29 @@ func handleChannels(chans <-chan ssh.NewChannel) {
    // "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...")
    }
    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)
    @@ -155,3 +173,27 @@ func handleChannels(chans <-chan ssh.NewChannel) {
    }(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)))
    }
  10. jpillora revised this gist Dec 2, 2014. 1 changed file with 48 additions and 24 deletions.
    72 changes: 48 additions & 24 deletions sshd.go
    Original file line number Diff line number Diff line change
    @@ -2,12 +2,16 @@ package main

    import (
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "net"
    "os/exec"
    "sync"

    "code.google.com/p/go.crypto/ssh"
    "code.google.com/p/go.crypto/ssh/terminal"
    "github.com/kr/pty"
    "golang.org/x/crypto/ssh"
    "golang.org/x/crypto/ssh/terminal"
    )

    func main() {
    @@ -80,8 +84,8 @@ func handleChannels(chans <-chan ssh.NewChannel) {
    // protocol intended. In the case of a shell, the type is
    // "session" and ServerShell may be used to present a simple
    // terminal interface.
    if newChannel.ChannelType() != "session" {
    newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
    if t := newChannel.ChannelType(); t != "session" {
    newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
    continue
    }
    channel, requests, err := newChannel.Accept()
    @@ -90,6 +94,44 @@ func handleChannels(chans <-chan ssh.NewChannel) {
    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.
    @@ -98,36 +140,18 @@ func handleChannels(chans <-chan ssh.NewChannel) {
    ok := false
    switch req.Type {
    case "shell":
    // We don't accept any commands (Payload),
    // only the default shell.
    if len(req.Payload) == 0 {
    // We don't accept any
    // commands, only the
    // default shell.
    ok = true
    log.Printf("accepting shell request...")
    }
    }

    if !ok {
    log.Printf("declining %s request...", req.Type)
    }

    req.Reply(ok, nil)
    }
    }(requests)

    //allocate a terminal for this channel
    log.Print("allocating terminal...")
    term := terminal.NewTerminal(channel, "> ")
    go func() {
    defer channel.Close()
    for {
    line, err := term.ReadLine()
    if err != nil {
    log.Printf("could not read line (%s)", err)
    break
    }
    log.Printf("recieved: %s", line)
    }
    }()
    }
    }
  11. jpillora created this gist Dec 1, 2014.
    133 changes: 133 additions & 0 deletions sshd.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,133 @@
    package main

    import (
    "fmt"
    "io/ioutil"
    "log"
    "net"

    "code.google.com/p/go.crypto/ssh"
    "code.google.com/p/go.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 newChannel.ChannelType() != "session" {
    newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
    continue
    }
    channel, requests, err := newChannel.Accept()
    if err != nil {
    log.Printf("could not accept channel (%s)", err)
    continue
    }

    // 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":
    if len(req.Payload) == 0 {
    // We don't accept any
    // commands, only the
    // default shell.
    ok = true
    log.Printf("accepting shell request...")
    }
    }

    if !ok {
    log.Printf("declining %s request...", req.Type)
    }

    req.Reply(ok, nil)
    }
    }(requests)

    //allocate a terminal for this channel
    log.Print("allocating terminal...")
    term := terminal.NewTerminal(channel, "> ")
    go func() {
    defer channel.Close()
    for {
    line, err := term.ReadLine()
    if err != nil {
    log.Printf("could not read line (%s)", err)
    break
    }
    log.Printf("recieved: %s", line)
    }
    }()
    }
    }