How to wait for Ctrl+C (session.Signals does not work as expected)
I'm trying to block the handler whenever a reverse port forwarding was requested. The tunnel should exist as long as the user stops the SSH session by entering the Ctrl+C sequence. There is a Signals function that I would expect to do this, but nothing arrives at the channel in my testing code:
func handler(s ssh.Session) {
// ...
if requestedRevPortForwarding { // set in the reversePortForwardingHandler using ctx.SetValue()
io.WriteString(s, "[*] Forwarding initialized!\n")
io.WriteString(s, "[*] Stop with Ctrl+C")
signals := make(chan ssh.Signal, 10)
s.Signals(signals)
go func() {
for {
fmt.Println(<-signals)
}
}()
}
<-s.Context().Done() // block
// ...
}
With this example the user can only exit the session by closing the terminal he's in. There must be a way to handle this with signals. I know that I can use terminals to get like a "quit" command, but I want to use the Ctrl+C approach here.
Any idea why this is not working or an alternative to solve this?
I have also been playing around with this project and I believe that signals are not yet fully implemented. I managed to work around this by using the "golang.org/x/term" package to handle the session Read/Writer which in fact will handle CTRL+C correctly.
Example:
Handler: ssh.Handler(func(s ssh.Session) {
io.WriteString(s, "Shell is ready.\nPress CTRL+C to terminate session.\n")
shell := term.NewTerminal(s, "")
shell.SetPrompt(string("> " + string(shell.Escape.Reset)))
for {
line, err := shell.ReadLine()
if err == io.EOF {
return
}
if err != nil {
io.WriteString(s, fmt.Sprintln(err))
continue
}
if line == "" {
continue
}
io.WriteString(s, line)
}
}),
I found an easier solution (that also supports Ctrl+D):
const KeyCtrlC = 3 // ASCII ETX -> Ctrl+C
const KeyCtrlD = 4 // ASCII EOT -> Ctrl+D
// ...
func HandleSession(session ssh.Session) {
// Hello there
// ...
for {
// we read one byte at once
c := []byte{0}
_, err = session.Read(c)
if err != nil {
// error handling ...
break
}
// we check if this byte is the ETX or EOT
// control character & end the loop
if c[0] == KeyCtrlC || c[0] == KeyCtrlD { break }
}
// ...
// Bye
}
// ...
Actually that's how they do it in golang.org/x/term ^^
EDIT: You could also pass ssh.NoPty() to ssh.ListenAndServe() so that no pseudo-terminal is allocated for a client. Then the SSH client itself handles Ctrl+C and Ctrl+D.