fix version
This commit is contained in:
2
go.mod
2
go.mod
@@ -5,7 +5,7 @@ go 1.24.0
|
|||||||
require (
|
require (
|
||||||
github.com/creack/pty/v2 v2.0.1
|
github.com/creack/pty/v2 v2.0.1
|
||||||
golang.org/x/crypto v0.43.0
|
golang.org/x/crypto v0.43.0
|
||||||
golang.org/x/term v0.37.0
|
golang.org/x/term v0.36.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require golang.org/x/sys v0.37.0 // indirect
|
require golang.org/x/sys v0.37.0 // indirect
|
||||||
|
|||||||
112
main.go
112
main.go
@@ -97,15 +97,32 @@ func handleChannel(channel ssh.Channel, requests <-chan *ssh.Request) {
|
|||||||
defer channel.Close()
|
defer channel.Close()
|
||||||
var ptmx *os.File
|
var ptmx *os.File
|
||||||
var termWidth, termHeight uint32 = 80, 24
|
var termWidth, termHeight uint32 = 80, 24
|
||||||
var term string
|
var clientTerm string
|
||||||
|
var ptyAllocated bool // Track if PTY is allocated
|
||||||
|
|
||||||
for req := range requests {
|
for req := range requests {
|
||||||
switch req.Type {
|
switch req.Type {
|
||||||
|
case "env":
|
||||||
|
// Handle environment requests (e.g., client TERM propagation)
|
||||||
|
if len(req.Payload) >= 7 && string(req.Payload[:7]) == "TERM=\x00" {
|
||||||
|
termLen := int(req.Payload[7])
|
||||||
|
if len(req.Payload) >= 8+termLen {
|
||||||
|
clientTerm = string(req.Payload[8 : 8+termLen])
|
||||||
|
log.Println("Client TERM from env:", clientTerm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req.Reply(true, nil)
|
||||||
case "pty-req":
|
case "pty-req":
|
||||||
|
// Allocate PTY early on pty-req, before shell/exec
|
||||||
|
if ptyAllocated {
|
||||||
|
req.Reply(false, nil)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if len(req.Payload) >= 4 {
|
if len(req.Payload) >= 4 {
|
||||||
termLen := binary.BigEndian.Uint32(req.Payload[0:4])
|
termLen := binary.BigEndian.Uint32(req.Payload[0:4])
|
||||||
if len(req.Payload) >= int(4+termLen+16) {
|
if len(req.Payload) >= int(4+termLen+16) {
|
||||||
term = string(req.Payload[4 : 4+termLen])
|
clientTerm = string(req.Payload[4 : 4+termLen])
|
||||||
log.Println("Client TERM:", term)
|
log.Println("Client TERM from pty-req:", clientTerm)
|
||||||
cols := binary.BigEndian.Uint32(req.Payload[4+termLen : 4+termLen+4])
|
cols := binary.BigEndian.Uint32(req.Payload[4+termLen : 4+termLen+4])
|
||||||
rows := binary.BigEndian.Uint32(req.Payload[4+termLen+4 : 4+termLen+8])
|
rows := binary.BigEndian.Uint32(req.Payload[4+termLen+4 : 4+termLen+8])
|
||||||
if cols > 0 {
|
if cols > 0 {
|
||||||
@@ -116,50 +133,81 @@ func handleChannel(channel ssh.Channel, requests <-chan *ssh.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
req.Reply(true, nil)
|
// Set default TERM if not provided (TUI-compatible)
|
||||||
case "window-change":
|
if clientTerm == "" {
|
||||||
width := binary.BigEndian.Uint32(req.Payload)
|
clientTerm = "xterm-256color"
|
||||||
height := binary.BigEndian.Uint32(req.Payload[4:])
|
|
||||||
if width > 0 {
|
|
||||||
termWidth = width
|
|
||||||
}
|
}
|
||||||
if height > 0 {
|
|
||||||
termHeight = height
|
|
||||||
}
|
|
||||||
if ptmx != nil {
|
|
||||||
pty.Setsize(ptmx, &pty.Winsize{Cols: uint16(termWidth), Rows: uint16(termHeight)})
|
|
||||||
}
|
|
||||||
req.Reply(true, nil)
|
|
||||||
case "shell":
|
|
||||||
req.Reply(true, nil)
|
|
||||||
command := os.Getenv("COMMAND")
|
command := os.Getenv("COMMAND")
|
||||||
if command == "" {
|
if command == "" {
|
||||||
command = "/app/tui"
|
command = "/app/tui"
|
||||||
}
|
}
|
||||||
cmd := exec.Command(command)
|
cmd := exec.Command(command)
|
||||||
envTerm := "TERM=xterm-256color"
|
envTerm := "TERM=" + clientTerm
|
||||||
if term != "" {
|
|
||||||
envTerm = "TERM=" + term
|
|
||||||
}
|
|
||||||
cmd.Env = []string{"PATH=/bin", envTerm}
|
cmd.Env = []string{"PATH=/bin", envTerm}
|
||||||
cmd.Dir = "/"
|
cmd.Dir = "/"
|
||||||
var err error
|
var err error
|
||||||
ptmx, err = pty.StartWithSize(cmd, &pty.Winsize{Cols: uint16(termWidth), Rows: uint16(termHeight)})
|
ptmx, err = pty.StartWithSize(cmd, &pty.Winsize{Cols: uint16(termWidth), Rows: uint16(termHeight)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("PTY start error:", err)
|
log.Println("PTY start error:", err)
|
||||||
|
req.Reply(false, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := term.MakeRaw(int(ptmx.Fd())); err != nil {
|
// Make raw mode on master (server side)
|
||||||
|
if _, err := term.MakeRaw(int(ptmx.Fd())); err != nil {
|
||||||
log.Println("MakeRaw master error:", err)
|
log.Println("MakeRaw master error:", err)
|
||||||
}
|
}
|
||||||
|
// Note: Slave (cmd side) is already raw via pty.Start, but ensure via setsid if needed
|
||||||
|
ptyAllocated = true
|
||||||
|
// Start I/O bridging immediately
|
||||||
go func() {
|
go func() {
|
||||||
defer ptmx.Close()
|
defer func() {
|
||||||
go io.Copy(channel, ptmx)
|
ptmx.Close()
|
||||||
go io.Copy(ptmx, channel)
|
channel.Close()
|
||||||
cmd.Wait()
|
}()
|
||||||
channel.Close()
|
// Bidirectional copy with error handling
|
||||||
|
done := make(chan error, 2)
|
||||||
|
go func() { done <- io.Copy(channel, ptmx) }()
|
||||||
|
go func() { done <- io.Copy(ptmx, channel) }()
|
||||||
|
<-done // Wait for one to finish
|
||||||
|
cmd.Process.Signal(os.Interrupt) // Graceful shutdown
|
||||||
|
<-done
|
||||||
}()
|
}()
|
||||||
|
req.Reply(true, nil)
|
||||||
|
// Wait for cmd to finish after PTY setup
|
||||||
|
go func() {
|
||||||
|
cmd.Wait()
|
||||||
|
channel.SendRequest("exit-status", false, []byte{0}) // Send exit status
|
||||||
|
}()
|
||||||
|
return // PTY session started, no more requests
|
||||||
|
case "window-change":
|
||||||
|
// Handle resizes post-PTY allocation
|
||||||
|
if ptyAllocated && ptmx != nil {
|
||||||
|
width := binary.BigEndian.Uint32(req.Payload)
|
||||||
|
height := binary.BigEndian.Uint32(req.Payload[4:])
|
||||||
|
if width > 0 {
|
||||||
|
termWidth = width
|
||||||
|
}
|
||||||
|
if height > 0 {
|
||||||
|
termHeight = height
|
||||||
|
}
|
||||||
|
pty.Setsize(ptmx, &pty.Winsize{Cols: uint16(termWidth), Rows: uint16(termHeight)})
|
||||||
|
}
|
||||||
|
req.Reply(true, nil)
|
||||||
|
case "shell":
|
||||||
|
// Only handle if no PTY (fallback, but PTY should be allocated first)
|
||||||
|
if !ptyAllocated {
|
||||||
|
req.Reply(true, nil)
|
||||||
|
// Fallback to original shell logic if needed
|
||||||
|
log.Println("Shell requested without PTY")
|
||||||
|
} else {
|
||||||
|
req.Reply(false, nil)
|
||||||
|
}
|
||||||
case "exec":
|
case "exec":
|
||||||
|
// Handle exec separately if no PTY
|
||||||
|
if ptyAllocated {
|
||||||
|
req.Reply(false, nil)
|
||||||
|
continue
|
||||||
|
}
|
||||||
req.Reply(true, nil)
|
req.Reply(true, nil)
|
||||||
command := string(req.Payload[4:])
|
command := string(req.Payload[4:])
|
||||||
runCommand(channel, command)
|
runCommand(channel, command)
|
||||||
@@ -171,6 +219,7 @@ func handleChannel(channel ssh.Channel, requests <-chan *ssh.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runCommand(channel ssh.Channel, command string) {
|
func runCommand(channel ssh.Channel, command string) {
|
||||||
|
defer channel.Close()
|
||||||
cmd := exec.Command("/bin/sh", "-c", command)
|
cmd := exec.Command("/bin/sh", "-c", command)
|
||||||
cmd.Env = []string{"PATH=/bin"}
|
cmd.Env = []string{"PATH=/bin"}
|
||||||
cmd.Dir = "/"
|
cmd.Dir = "/"
|
||||||
@@ -193,8 +242,9 @@ func runCommand(channel ssh.Channel, command string) {
|
|||||||
log.Println("Start error:", err)
|
log.Println("Start error:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go io.Copy(stdin, channel)
|
go func() { io.Copy(stdin, channel); stdin.Close() }()
|
||||||
go io.Copy(channel, stdout)
|
go func() { io.Copy(channel, stdout); stdout.Close() }()
|
||||||
go io.Copy(channel, stderr)
|
go func() { io.Copy(channel, stderr); stderr.Close() }()
|
||||||
cmd.Wait()
|
cmd.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user