Skip to content

Instantly share code, notes, and snippets.

@ivan1993spb
Forked from jpillora/sshd.go
Created April 17, 2020 15:07
Show Gist options
  • Save ivan1993spb/5a92dd48f5842f95fc00ff3af11e9055 to your computer and use it in GitHub Desktop.
Save ivan1993spb/5a92dd48f5842f95fc00ff3af11e9055 to your computer and use it in GitHub Desktop.

Revisions

  1. @jpillora jpillora revised this gist Jun 22, 2016. No changes.
  2. @jpillora jpillora revised this gist Jun 22, 2016. No changes.
  3. @jpillora 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 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 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 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 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 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 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 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 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)
    }
    }()
    }
    }