Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ var (
// ContextKeyPublicKey is a context key for use with Contexts in this package.
// The associated value will be of type PublicKey.
ContextKeyPublicKey = &contextKey{"public-key"}

// ContextKeySession is a context key for use with Contexts in this package.
// The associated value will be of type Session.
ContextKeySession = &contextKey{"session"}
)

// Context is a package specific context interface. It exposes connection
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ go 1.17

require (
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
github.com/creack/pty v1.1.21
github.com/u-root/u-root v0.11.0
golang.org/x/crypto v0.17.0
)

require golang.org/x/sys v0.15.0 // indirect
require golang.org/x/sys v0.16.0
338 changes: 337 additions & 1 deletion go.sum

Large diffs are not rendered by default.

30 changes: 29 additions & 1 deletion options.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func HostKeyPEM(bytes []byte) Option {
// denying PTY requests.
func NoPty() Option {
return func(srv *Server) error {
srv.PtyCallback = func(ctx Context, pty Pty) bool {
srv.PtyCallback = func(Context, Pty) bool {
return false
}
return nil
Expand All @@ -82,3 +82,31 @@ func WrapConn(fn ConnCallback) Option {
return nil
}
}

var contextKeyEmulatePty = &contextKey{"emulate-pty"}

func emulatePtyHandler(ctx Context, _ Session, _ Pty) (func() error, error) {
ctx.SetValue(contextKeyEmulatePty, true)
return func() error { return nil }, nil
}

// EmulatePty returns a functional option that fakes a PTY. It uses PtyWriter
// underneath.
func EmulatePty() Option {
return func(s *Server) error {
s.PtyHandler = emulatePtyHandler
return nil
}
}

// AllocatePty returns a functional option that allocates a PTY. Implementers
// who wish to use an actual PTY should use this along with the platform
// specific PTY implementation defined in pty_*.go.
func AllocatePty() Option {
return func(s *Server) error {
s.PtyHandler = func(_ Context, s Session, pty Pty) (func() error, error) {
return s.(*session).ptyAllocate(pty.Term, pty.Window, pty.Modes)
}
return nil
}
}
4 changes: 4 additions & 0 deletions pty.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ package ssh

import (
"bytes"
"errors"
"io"
)

// ErrUnsupported is returned when the platform does not support PTY.
var ErrUnsupported = errors.New("pty unsupported")

// NewPtyWriter creates a writer that handles when the session has a active
// PTY, replacing the \n with \r\n.
func NewPtyWriter(w io.Writer) io.Writer {
Expand Down
31 changes: 31 additions & 0 deletions pty_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//go:build !linux && !darwin && !freebsd && !dragonfly && !netbsd && !openbsd && !solaris
// +build !linux,!darwin,!freebsd,!dragonfly,!netbsd,!openbsd,!solaris

// TODO: support Windows
package ssh

import (
"golang.org/x/crypto/ssh"
)

type impl struct{}

func (i *impl) Read(p []byte) (n int, err error) {
return 0, ErrUnsupported
}

func (i *impl) Write(p []byte) (n int, err error) {
return 0, ErrUnsupported
}

func (i *impl) Resize(w int, h int) error {
return ErrUnsupported
}

func (i *impl) Close() error {
return nil
}

func newPty(Context, string, Window, ssh.TerminalModes) (impl, error) {
return impl{}, ErrUnsupported
}
181 changes: 181 additions & 0 deletions pty_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build darwin dragonfly freebsd linux netbsd openbsd solaris

package ssh

import (
"fmt"
"os"

"github.com/creack/pty"
"github.com/u-root/u-root/pkg/termios"
"golang.org/x/crypto/ssh"
"golang.org/x/sys/unix"
)

type impl struct {
// Master is the master PTY file descriptor.
Master *os.File

// Slave is the slave PTY file descriptor.
Slave *os.File

// Name is the name of the slave PTY.
Name string
}

// Read implements ptyInterface.
func (i *impl) Read(p []byte) (n int, err error) {
return i.Master.Read(p)
}

// Write implements ptyInterface.
func (i *impl) Write(p []byte) (n int, err error) {
return i.Master.Write(p)
}

func (i *impl) Close() error {
if err := i.Master.Close(); err != nil {
return err
}
return i.Slave.Close()
}

func (i *impl) Resize(w int, h int) (rErr error) {
conn, err := i.Master.SyscallConn()
if err != nil {
return err
}

return conn.Control(func(fd uintptr) {
rErr = termios.SetWinSize(fd, &termios.Winsize{
Winsize: unix.Winsize{
Row: uint16(h),
Col: uint16(w),
},
})
})
}

func newPty(_ Context, _ string, win Window, modes ssh.TerminalModes) (_ impl, rErr error) {
ptm, pts, err := pty.Open()
if err != nil {
return impl{}, err
}

conn, err := ptm.SyscallConn()
if err != nil {
return impl{}, err
}

if err := conn.Control(func(fd uintptr) {
rErr = applyTerminalModesToFd(fd, win.Width, win.Height, modes)
}); err != nil {
return impl{}, err
}

return impl{Master: ptm, Slave: pts, Name: pts.Name()}, rErr
}

func applyTerminalModesToFd(fd uintptr, width int, height int, modes ssh.TerminalModes) error {
// Get the current TTY configuration.
tios, err := termios.GTTY(int(fd))
if err != nil {
return fmt.Errorf("GTTY: %w", err)
}

// Apply the modes from the SSH request.
tios.Row = height
tios.Col = width

for c, v := range modes {
if c == ssh.TTY_OP_ISPEED {
tios.Ispeed = int(v)
continue
}
if c == ssh.TTY_OP_OSPEED {
tios.Ospeed = int(v)
continue
}
k, ok := terminalModeFlagNames[c]
if !ok {
continue
}
if _, ok := tios.CC[k]; ok {
tios.CC[k] = uint8(v)
continue
}
if _, ok := tios.Opts[k]; ok {
tios.Opts[k] = v > 0
continue
}
}

// Save the new TTY configuration.
if _, err := tios.STTY(int(fd)); err != nil {
return fmt.Errorf("STTY: %w", err)
}

return nil
}

// terminalModeFlagNames maps the SSH terminal mode flags to mnemonic
// names used by the termios package.
var terminalModeFlagNames = map[uint8]string{
ssh.VINTR: "intr",
ssh.VQUIT: "quit",
ssh.VERASE: "erase",
ssh.VKILL: "kill",
ssh.VEOF: "eof",
ssh.VEOL: "eol",
ssh.VEOL2: "eol2",
ssh.VSTART: "start",
ssh.VSTOP: "stop",
ssh.VSUSP: "susp",
ssh.VDSUSP: "dsusp",
ssh.VREPRINT: "rprnt",
ssh.VWERASE: "werase",
ssh.VLNEXT: "lnext",
ssh.VFLUSH: "flush",
ssh.VSWTCH: "swtch",
ssh.VSTATUS: "status",
ssh.VDISCARD: "discard",
ssh.IGNPAR: "ignpar",
ssh.PARMRK: "parmrk",
ssh.INPCK: "inpck",
ssh.ISTRIP: "istrip",
ssh.INLCR: "inlcr",
ssh.IGNCR: "igncr",
ssh.ICRNL: "icrnl",
ssh.IUCLC: "iuclc",
ssh.IXON: "ixon",
ssh.IXANY: "ixany",
ssh.IXOFF: "ixoff",
ssh.IMAXBEL: "imaxbel",
ssh.IUTF8: "iutf8",
ssh.ISIG: "isig",
ssh.ICANON: "icanon",
ssh.XCASE: "xcase",
ssh.ECHO: "echo",
ssh.ECHOE: "echoe",
ssh.ECHOK: "echok",
ssh.ECHONL: "echonl",
ssh.NOFLSH: "noflsh",
ssh.TOSTOP: "tostop",
ssh.IEXTEN: "iexten",
ssh.ECHOCTL: "echoctl",
ssh.ECHOKE: "echoke",
ssh.PENDIN: "pendin",
ssh.OPOST: "opost",
ssh.OLCUC: "olcuc",
ssh.ONLCR: "onlcr",
ssh.OCRNL: "ocrnl",
ssh.ONOCR: "onocr",
ssh.ONLRET: "onlret",
ssh.CS7: "cs7",
ssh.CS8: "cs8",
ssh.PARENB: "parenb",
ssh.PARODD: "parodd",
ssh.TTY_OP_ISPEED: "tty_op_ispeed",
ssh.TTY_OP_OSPEED: "tty_op_ospeed",
}
16 changes: 10 additions & 6 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ type Server struct {
KeyboardInteractiveHandler KeyboardInteractiveHandler // keyboard-interactive authentication handler
PasswordHandler PasswordHandler // password authentication handler
PublicKeyHandler PublicKeyHandler // public key authentication handler
PtyCallback PtyCallback // callback for allowing PTY sessions, allows all if nil
PtyCallback PtyCallback // callback for allocating and allowing PTY sessions, ssh.EmulatePtyCallback if nil
PtyHandler PtyHandler // pty allocation handler, ssh.emulatePtyHandler if nil
ConnCallback ConnCallback // optional callback for wrapping net.Conn before handling
LocalPortForwardingCallback LocalPortForwardingCallback // callback for allowing local port forwarding, denies all if nil
ReversePortForwardingCallback ReversePortForwardingCallback // callback for allowing reverse port forwarding, denies all if nil
Expand Down Expand Up @@ -116,8 +117,8 @@ func (srv *Server) ensureHandlers() {
}

func (srv *Server) config(ctx Context) *gossh.ServerConfig {
srv.mu.RLock()
defer srv.mu.RUnlock()
srv.mu.Lock()
defer srv.mu.Unlock()

var config *gossh.ServerConfig
if srv.ServerConfigCallback == nil {
Expand All @@ -131,6 +132,9 @@ func (srv *Server) config(ctx Context) *gossh.ServerConfig {
if srv.PasswordHandler == nil && srv.PublicKeyHandler == nil && srv.KeyboardInteractiveHandler == nil {
config.NoClientAuth = true
}
if srv.PtyHandler == nil {
srv.PtyHandler = emulatePtyHandler
}
if srv.Version != "" {
config.ServerVersion = "SSH-2.0-" + srv.Version
}
Expand Down Expand Up @@ -304,7 +308,7 @@ func (srv *Server) HandleConn(newConn net.Conn) {

ctx.SetValue(ContextKeyConn, sshConn)
applyConnMetadata(ctx, sshConn)
//go gossh.DiscardRequests(reqs)
// go gossh.DiscardRequests(reqs)
go srv.handleRequests(ctx, reqs)
for ch := range chans {
handler := srv.ChannelHandlers[ch.ChannelType()]
Expand Down Expand Up @@ -381,8 +385,8 @@ func (srv *Server) SetOption(option Option) error {
// internal method. We can't actually lock here because if something calls
// (as an example) AddHostKey, it will deadlock.

//srv.mu.Lock()
//defer srv.mu.Unlock()
// srv.mu.Lock()
// defer srv.mu.Unlock()

return option(srv)
}
Expand Down
Loading