diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b304922ae..39ed5d875 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,7 @@ Latest ================ - Add SSH-style escape sequences to shell sessions (~. to terminate, ~? for help) - Fix port forwarding sessions silently dying after WebSocket reconnection +- Fix shell sessions terminating on macOS/BSD when Ctrl+Y is pressed by disabling the DSUSP terminal control character 1.2.814.0 ================ diff --git a/src/sessionmanagerplugin/session/shellsession/shellsession_bsd.go b/src/sessionmanagerplugin/session/shellsession/shellsession_bsd.go new file mode 100644 index 000000000..668da917a --- /dev/null +++ b/src/sessionmanagerplugin/session/shellsession/shellsession_bsd.go @@ -0,0 +1,32 @@ +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may not +// use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +// either express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +//go:build darwin || freebsd || netbsd || openbsd +// +build darwin freebsd netbsd openbsd + +// Package shellsession starts shell session. +package shellsession + +import "bytes" + +// disableDelayedSuspend disables the DSUSP terminal special character (Ctrl+Y by +// default) on BSD-derived systems, including macOS. +// +// In cbreak mode the terminal driver still acts on DSUSP, so pressing Ctrl+Y +// triggers a delayed suspend that causes the Stdin read to fail with +// "read /dev/stdin: resource temporarily unavailable" and the session to +// terminate. Linux does not implement DSUSP, so this is a no-op there. +// See https://github.com/aws/session-manager-plugin/issues/29. +func (s *ShellSession) disableDelayedSuspend() { + setState(bytes.NewBufferString("dsusp undef")) +} diff --git a/src/sessionmanagerplugin/session/shellsession/shellsession_linux.go b/src/sessionmanagerplugin/session/shellsession/shellsession_linux.go new file mode 100644 index 000000000..dd1bf6a8d --- /dev/null +++ b/src/sessionmanagerplugin/session/shellsession/shellsession_linux.go @@ -0,0 +1,24 @@ +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may not +// use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +// either express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +//go:build linux +// +build linux + +// Package shellsession starts shell session. +package shellsession + +// disableDelayedSuspend is a no-op on Linux, which does not implement the DSUSP +// (delayed suspend) terminal special character. The "dsusp" setting is rejected +// by stty on Linux, so it must only be applied on BSD-derived systems. See the +// BSD implementation and https://github.com/aws/session-manager-plugin/issues/29. +func (s *ShellSession) disableDelayedSuspend() {} diff --git a/src/sessionmanagerplugin/session/shellsession/shellsession_unix.go b/src/sessionmanagerplugin/session/shellsession/shellsession_unix.go index b050d8c7d..db3482d2a 100644 --- a/src/sessionmanagerplugin/session/shellsession/shellsession_unix.go +++ b/src/sessionmanagerplugin/session/shellsession/shellsession_unix.go @@ -23,6 +23,7 @@ import ( "errors" "os" "os/exec" + "strings" "time" "github.com/aws/session-manager-plugin/src/log" @@ -34,6 +35,7 @@ func (s *ShellSession) disableEchoAndInputBuffering() { getState(&s.originalSttyState) setState(bytes.NewBufferString("cbreak")) setState(bytes.NewBufferString("-echo")) + s.disableDelayedSuspend() } // getState gets current state of terminal @@ -44,9 +46,12 @@ func getState(state *bytes.Buffer) error { return cmd.Run() } -// setState sets the new settings to terminal +// setState sets the new settings to terminal. The buffer may contain several +// whitespace-separated stty operands (for example "dsusp undef"), which must be +// passed to stty as individual arguments rather than a single operand. func setState(state *bytes.Buffer) error { - cmd := exec.Command("stty", state.String()) + args := strings.Fields(state.String()) + cmd := exec.Command("stty", args...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout return cmd.Run()