forked from gliderlabs/ssh
-
Notifications
You must be signed in to change notification settings - Fork 8
feat: allocate real pty #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
97a8c4d
feat: allocate real pty
aymanbagabas d07856e
fix: update pty godoc
aymanbagabas 7ffdd48
fix: convert pty handlers to server options
aymanbagabas 1a1da3b
fix: consume resize events on pty
aymanbagabas 69a7781
feat: support windows conpty
aymanbagabas 9a55991
feat: add pty start process example
aymanbagabas c82fc09
fix: return tty name
aymanbagabas f8252fe
fix: ptystart example for unix
aymanbagabas cb73e2b
fix: imports
caarlos0 3b52b26
fix: update
caarlos0 23e5fa7
chore: deps
caarlos0 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"log" | ||
"os" | ||
"os/exec" | ||
"runtime" | ||
"time" | ||
|
||
"github.com/charmbracelet/ssh" | ||
) | ||
|
||
func main() { | ||
ssh.Handle(func(s ssh.Session) { | ||
log.Printf("connected %s %s %q", s.User(), s.RemoteAddr(), s.RawCommand()) | ||
defer log.Printf("disconnected %s %s", s.User(), s.RemoteAddr()) | ||
|
||
pty, _, ok := s.Pty() | ||
if !ok { | ||
io.WriteString(s, "No PTY requested.\n") | ||
s.Exit(1) | ||
return | ||
} | ||
|
||
name := "bash" | ||
if runtime.GOOS == "windows" { | ||
name = "powershell.exe" | ||
} | ||
cmd := exec.Command(name) | ||
cmd.Env = append(os.Environ(), "SSH_TTY="+pty.Name(), fmt.Sprintf("TERM=%s", pty.Term)) | ||
if err := pty.Start(cmd); err != nil { | ||
fmt.Fprintln(s, err.Error()) | ||
s.Exit(1) | ||
return | ||
} | ||
|
||
if runtime.GOOS == "windows" { | ||
// ProcessState gets populated by pty.Start waiting on the process | ||
// to exit. | ||
for cmd.ProcessState == nil { | ||
time.Sleep(100 * time.Millisecond) | ||
} | ||
|
||
s.Exit(cmd.ProcessState.ExitCode()) | ||
} else { | ||
if err := cmd.Wait(); err != nil { | ||
fmt.Fprintln(s, err) | ||
s.Exit(cmd.ProcessState.ExitCode()) | ||
} | ||
} | ||
}) | ||
|
||
log.Println("starting ssh server on port 2222...") | ||
if err := ssh.ListenAndServe(":2222", nil, ssh.AllocatePty()); err != nil && err != ssh.ErrServerClosed { | ||
log.Fatal(err) | ||
} | ||
} |
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
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
Large diffs are not rendered by default.
Oops, something went wrong.
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
//go:build !linux && !darwin && !freebsd && !dragonfly && !netbsd && !openbsd && !solaris && !windows | ||
// +build !linux,!darwin,!freebsd,!dragonfly,!netbsd,!openbsd,!solaris,!windows | ||
|
||
package ssh | ||
|
||
import ( | ||
"os/exec" | ||
|
||
"golang.org/x/crypto/ssh" | ||
) | ||
|
||
type impl struct{} | ||
|
||
func (i *impl) IsZero() bool { | ||
return true | ||
} | ||
|
||
func (i *impl) Name() string { | ||
return "" | ||
} | ||
|
||
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 (*impl) start(*exec.Cmd) error { | ||
return ErrUnsupported | ||
} | ||
|
||
func newPty(Context, string, Window, ssh.TerminalModes) (impl, error) { | ||
return impl{}, ErrUnsupported | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris | ||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris | ||
|
||
package ssh | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"syscall" | ||
|
||
"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 | ||
} | ||
|
||
func (i *impl) IsZero() bool { | ||
return i.Master == nil && i.Slave == nil | ||
} | ||
|
||
// Name returns the name of the slave PTY. | ||
func (i *impl) Name() string { | ||
return i.Slave.Name() | ||
} | ||
|
||
// 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 (i *impl) start(c *exec.Cmd) error { | ||
c.Stdin, c.Stdout, c.Stderr = i.Slave, i.Slave, i.Slave | ||
if c.SysProcAttr == nil { | ||
c.SysProcAttr = &syscall.SysProcAttr{} | ||
} | ||
c.SysProcAttr.Setctty = true | ||
c.SysProcAttr.Setsid = true | ||
return c.Start() | ||
} | ||
|
||
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}, 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", | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.