ssh icon indicating copy to clipboard operation
ssh copied to clipboard

How to wait for Ctrl+C (session.Signals does not work as expected)

Open MexHigh opened this issue 2 years ago • 3 comments

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?

MexHigh avatar Jan 18 '24 12:01 MexHigh

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)
	}
}),

hugorosario avatar Feb 13 '24 14:02 hugorosario

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.

ChaoticByte avatar Jun 28 '24 18:06 ChaoticByte