src/sessionmanagerplugin/session/shellsession/shellsession.go (96 lines of code) (raw):
// 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.
// Package shellsession starts shell session.
package shellsession
import (
"bytes"
"encoding/json"
"os"
"os/signal"
"time"
"github.com/aws/session-manager-plugin/src/config"
"github.com/aws/session-manager-plugin/src/log"
"github.com/aws/session-manager-plugin/src/message"
"github.com/aws/session-manager-plugin/src/sessionmanagerplugin/session"
"github.com/aws/session-manager-plugin/src/sessionmanagerplugin/session/sessionutil"
"golang.org/x/crypto/ssh/terminal"
)
const (
ResizeSleepInterval = time.Millisecond * 500
StdinBufferLimit = 1024
)
type ShellSession struct {
session.Session
// SizeData is used to store size data at session level to compare with new size.
SizeData message.SizeData
originalSttyState bytes.Buffer
}
var GetTerminalSizeCall = func(fd int) (width int, height int, err error) {
return terminal.GetSize(fd)
}
func init() {
session.Register(&ShellSession{})
}
// Name is the session name used in the plugin
func (ShellSession) Name() string {
return config.ShellPluginName
}
func (s *ShellSession) Initialize(log log.T, sessionVar *session.Session) {
s.Session = *sessionVar
s.DataChannel.RegisterOutputStreamHandler(s.ProcessStreamMessagePayload, true)
s.DataChannel.GetWsChannel().SetOnMessage(
func(input []byte) {
s.DataChannel.OutputMessageHandler(log, s.Stop, s.SessionId, input)
})
}
// StartSession takes input and write it to data channel
func (s *ShellSession) SetSessionHandlers(log log.T) (err error) {
// handle re-size
s.handleTerminalResize(log)
// handle control signals
s.handleControlSignals(log)
//handles keyboard input
err = s.handleKeyboardInput(log)
return
}
// handleControlSignals handles control signals when given by user
func (s *ShellSession) handleControlSignals(log log.T) {
go func() {
signals := make(chan os.Signal, 1)
signal.Notify(signals, sessionutil.ControlSignals...)
for {
sig := <-signals
if b, ok := sessionutil.SignalsByteMap[sig]; ok {
if err := s.DataChannel.SendInputDataMessage(log, message.Output, []byte{b}); err != nil {
log.Errorf("Failed to send control signals: %v", err)
}
}
}
}()
}
// handleTerminalResize checks size of terminal every 500ms and sends size data.
func (s *ShellSession) handleTerminalResize(log log.T) {
var (
width int
height int
inputSizeData []byte
err error
)
go func() {
for {
// If running from IDE GetTerminalSizeCall will not work. Supply a fixed width and height value.
if width, height, err = GetTerminalSizeCall(int(os.Stdout.Fd())); err != nil {
width = 300
height = 100
log.Errorf("Could not get size of the terminal: %s, using width %d height %d", err, width, height)
}
if s.SizeData.Rows != uint32(height) || s.SizeData.Cols != uint32(width) {
sizeData := message.SizeData{
Cols: uint32(width),
Rows: uint32(height),
}
s.SizeData = sizeData
if inputSizeData, err = json.Marshal(sizeData); err != nil {
log.Errorf("Cannot marshall size data: %v", err)
}
log.Debugf("Sending input size data: %s", inputSizeData)
if err = s.DataChannel.SendInputDataMessage(log, message.Size, inputSizeData); err != nil {
log.Errorf("Failed to Send size data: %v", err)
}
}
// repeating this loop for every 500ms
time.Sleep(ResizeSleepInterval)
}
}()
}
// ProcessStreamMessagePayload prints payload received on datachannel to console
func (s ShellSession) ProcessStreamMessagePayload(log log.T, outputMessage message.ClientMessage) (isHandlerReady bool, err error) {
s.DisplayMode.DisplayMessage(log, outputMessage)
return true, nil
}