2022-07-01 21:17:19 +02:00
|
|
|
package remote
|
|
|
|
|
|
|
|
import (
|
2022-08-24 11:14:16 +02:00
|
|
|
"bytes"
|
2022-07-02 02:38:36 +02:00
|
|
|
"context"
|
2022-07-07 07:46:22 +02:00
|
|
|
"encoding/base64"
|
2022-07-07 04:01:00 +02:00
|
|
|
"errors"
|
2022-07-01 21:17:19 +02:00
|
|
|
"fmt"
|
2022-08-19 22:23:00 +02:00
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2022-08-17 21:24:09 +02:00
|
|
|
"path"
|
2022-08-24 06:05:49 +02:00
|
|
|
"strconv"
|
2022-08-17 21:24:09 +02:00
|
|
|
"strings"
|
2022-07-01 21:17:19 +02:00
|
|
|
"sync"
|
2022-08-19 22:23:00 +02:00
|
|
|
"syscall"
|
|
|
|
"time"
|
2022-07-01 21:17:19 +02:00
|
|
|
|
2022-08-19 22:23:00 +02:00
|
|
|
"github.com/creack/pty"
|
2022-08-24 11:14:16 +02:00
|
|
|
"github.com/google/uuid"
|
2022-07-01 21:17:19 +02:00
|
|
|
"github.com/scripthaus-dev/mshell/pkg/base"
|
|
|
|
"github.com/scripthaus-dev/mshell/pkg/packet"
|
|
|
|
"github.com/scripthaus-dev/mshell/pkg/shexec"
|
2022-07-02 02:38:36 +02:00
|
|
|
"github.com/scripthaus-dev/sh2-server/pkg/sstore"
|
2022-07-01 21:17:19 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
const RemoteTypeMShell = "mshell"
|
2022-07-07 04:01:00 +02:00
|
|
|
const DefaultTermRows = 25
|
|
|
|
const DefaultTermCols = 80
|
|
|
|
const DefaultTerm = "xterm-256color"
|
2022-08-19 22:23:00 +02:00
|
|
|
const DefaultMaxPtySize = 1024 * 1024
|
2022-07-01 21:17:19 +02:00
|
|
|
|
2022-08-17 21:24:09 +02:00
|
|
|
const MShellServerCommand = `
|
|
|
|
PATH=$PATH:~/.mshell;
|
|
|
|
which mshell > /dev/null;
|
|
|
|
if [[ "$?" -ne 0 ]]
|
|
|
|
then
|
|
|
|
printf "\n##N{\"type\": \"init\", \"notfound\": true, \"uname\": \"%s | %s\"}\n" "$(uname -s)" "$(uname -m)"
|
|
|
|
else
|
|
|
|
mshell --server
|
|
|
|
fi
|
|
|
|
`
|
|
|
|
|
2022-07-02 02:38:36 +02:00
|
|
|
const (
|
|
|
|
StatusInit = "init"
|
|
|
|
StatusConnected = "connected"
|
|
|
|
StatusDisconnected = "disconnected"
|
|
|
|
StatusError = "error"
|
|
|
|
)
|
|
|
|
|
|
|
|
var GlobalStore *Store
|
|
|
|
|
2022-07-01 23:57:42 +02:00
|
|
|
type Store struct {
|
2022-08-20 02:14:53 +02:00
|
|
|
Lock *sync.Mutex
|
|
|
|
Map map[string]*MShellProc // key=remoteid
|
2022-07-01 23:57:42 +02:00
|
|
|
}
|
|
|
|
|
2022-08-30 01:31:06 +02:00
|
|
|
type RemoteRuntimeState struct {
|
2022-08-17 00:08:28 +02:00
|
|
|
RemoteType string `json:"remotetype"`
|
|
|
|
RemoteId string `json:"remoteid"`
|
|
|
|
PhysicalId string `json:"physicalremoteid"`
|
2022-08-19 22:23:00 +02:00
|
|
|
RemoteAlias string `json:"remotealias,omitempty"`
|
2022-08-17 00:08:28 +02:00
|
|
|
RemoteCanonicalName string `json:"remotecanonicalname"`
|
|
|
|
RemoteVars map[string]string `json:"remotevars"`
|
|
|
|
Status string `json:"status"`
|
2022-08-19 22:23:00 +02:00
|
|
|
ErrorStr string `json:"errorstr,omitempty"`
|
2022-08-17 00:08:28 +02:00
|
|
|
DefaultState *sstore.RemoteState `json:"defaultstate"`
|
2022-08-21 21:31:29 +02:00
|
|
|
ConnectMode string `json:"connectmode"`
|
2022-07-05 07:18:01 +02:00
|
|
|
}
|
|
|
|
|
2022-08-30 01:31:06 +02:00
|
|
|
func (state RemoteRuntimeState) IsConnected() bool {
|
2022-08-24 22:21:54 +02:00
|
|
|
return state.Status == StatusConnected
|
|
|
|
}
|
|
|
|
|
2022-08-30 01:31:06 +02:00
|
|
|
func (state RemoteRuntimeState) GetBaseDisplayName() string {
|
|
|
|
if state.RemoteAlias != "" {
|
|
|
|
return state.RemoteAlias
|
|
|
|
}
|
|
|
|
return state.RemoteCanonicalName
|
|
|
|
}
|
|
|
|
|
|
|
|
func (state RemoteRuntimeState) GetDisplayName(rptr *sstore.RemotePtrType) string {
|
|
|
|
name := state.GetBaseDisplayName()
|
|
|
|
if rptr == nil {
|
|
|
|
return name
|
|
|
|
}
|
|
|
|
if rptr.Name != "" {
|
|
|
|
name = name + ":" + rptr.Name
|
|
|
|
}
|
|
|
|
if rptr.OwnerId != "" {
|
|
|
|
name = "@" + rptr.OwnerId + ":" + name
|
|
|
|
}
|
|
|
|
return name
|
|
|
|
}
|
|
|
|
|
2022-07-01 21:17:19 +02:00
|
|
|
type MShellProc struct {
|
2022-07-01 23:57:42 +02:00
|
|
|
Lock *sync.Mutex
|
2022-07-02 02:38:36 +02:00
|
|
|
Remote *sstore.RemoteType
|
2022-07-01 23:57:42 +02:00
|
|
|
|
|
|
|
// runtime
|
2022-08-19 22:23:00 +02:00
|
|
|
Status string
|
|
|
|
ServerProc *shexec.ClientProc
|
|
|
|
UName string
|
|
|
|
Err error
|
|
|
|
ControllingPty *os.File
|
2022-07-08 07:13:45 +02:00
|
|
|
|
|
|
|
RunningCmds []base.CommandKey
|
2022-07-01 21:17:19 +02:00
|
|
|
}
|
|
|
|
|
2022-07-02 02:38:36 +02:00
|
|
|
func LoadRemotes(ctx context.Context) error {
|
|
|
|
GlobalStore = &Store{
|
|
|
|
Lock: &sync.Mutex{},
|
|
|
|
Map: make(map[string]*MShellProc),
|
|
|
|
}
|
|
|
|
allRemotes, err := sstore.GetAllRemotes(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, remote := range allRemotes {
|
|
|
|
msh := MakeMShell(remote)
|
2022-07-06 01:54:49 +02:00
|
|
|
GlobalStore.Map[remote.RemoteId] = msh
|
2022-08-21 21:31:29 +02:00
|
|
|
if remote.ConnectMode == sstore.ConnectModeStartup {
|
2022-07-02 02:38:36 +02:00
|
|
|
go msh.Launch()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-07-06 01:54:49 +02:00
|
|
|
func GetRemoteByName(name string) *MShellProc {
|
2022-07-02 02:38:36 +02:00
|
|
|
GlobalStore.Lock.Lock()
|
|
|
|
defer GlobalStore.Lock.Unlock()
|
2022-07-06 01:54:49 +02:00
|
|
|
for _, msh := range GlobalStore.Map {
|
2022-08-17 00:08:28 +02:00
|
|
|
if msh.Remote.RemoteAlias == name || msh.Remote.GetName() == name {
|
2022-07-06 01:54:49 +02:00
|
|
|
return msh
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetRemoteById(remoteId string) *MShellProc {
|
|
|
|
GlobalStore.Lock.Lock()
|
|
|
|
defer GlobalStore.Lock.Unlock()
|
|
|
|
return GlobalStore.Map[remoteId]
|
2022-07-02 02:38:36 +02:00
|
|
|
}
|
2022-07-01 23:57:42 +02:00
|
|
|
|
2022-08-30 01:31:06 +02:00
|
|
|
func ResolveRemoteRef(remoteRef string) *RemoteRuntimeState {
|
2022-08-24 11:14:16 +02:00
|
|
|
GlobalStore.Lock.Lock()
|
|
|
|
defer GlobalStore.Lock.Unlock()
|
|
|
|
|
|
|
|
_, err := uuid.Parse(remoteRef)
|
|
|
|
if err == nil {
|
|
|
|
msh := GlobalStore.Map[remoteRef]
|
|
|
|
if msh != nil {
|
2022-08-30 01:31:06 +02:00
|
|
|
state := msh.GetRemoteRuntimeState()
|
2022-08-24 11:14:16 +02:00
|
|
|
return &state
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
for _, msh := range GlobalStore.Map {
|
|
|
|
if msh.Remote.RemoteAlias == remoteRef || msh.Remote.RemoteCanonicalName == remoteRef {
|
2022-08-30 01:31:06 +02:00
|
|
|
state := msh.GetRemoteRuntimeState()
|
2022-08-24 11:14:16 +02:00
|
|
|
return &state
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-08-23 01:00:25 +02:00
|
|
|
func unquoteDQBashString(str string) (string, bool) {
|
|
|
|
if len(str) < 2 {
|
|
|
|
return str, false
|
|
|
|
}
|
|
|
|
if str[0] != '"' || str[len(str)-1] != '"' {
|
|
|
|
return str, false
|
|
|
|
}
|
|
|
|
rtn := make([]byte, 0, len(str)-2)
|
|
|
|
for idx := 1; idx < len(str)-1; idx++ {
|
|
|
|
ch := str[idx]
|
|
|
|
if ch == '"' {
|
|
|
|
return str, false
|
|
|
|
}
|
|
|
|
if ch == '\\' {
|
|
|
|
if idx == len(str)-2 {
|
|
|
|
return str, false
|
|
|
|
}
|
|
|
|
nextCh := str[idx+1]
|
|
|
|
if nextCh == '\n' {
|
|
|
|
idx++
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if nextCh == '$' || nextCh == '"' || nextCh == '\\' || nextCh == '`' {
|
|
|
|
idx++
|
|
|
|
rtn = append(rtn, nextCh)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
rtn = append(rtn, '\\')
|
|
|
|
continue
|
|
|
|
} else {
|
|
|
|
rtn = append(rtn, ch)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return string(rtn), true
|
|
|
|
}
|
|
|
|
|
2022-08-24 06:05:49 +02:00
|
|
|
func makeShortHost(host string) string {
|
|
|
|
dotIdx := strings.Index(host, ".")
|
|
|
|
if dotIdx == -1 {
|
|
|
|
return host
|
|
|
|
}
|
|
|
|
return host[0:dotIdx]
|
|
|
|
}
|
|
|
|
|
2022-08-30 01:31:06 +02:00
|
|
|
func (proc *MShellProc) GetRemoteRuntimeState() RemoteRuntimeState {
|
2022-08-23 23:01:52 +02:00
|
|
|
proc.Lock.Lock()
|
|
|
|
defer proc.Lock.Unlock()
|
2022-08-30 01:31:06 +02:00
|
|
|
state := RemoteRuntimeState{
|
2022-08-23 23:01:52 +02:00
|
|
|
RemoteType: proc.Remote.RemoteType,
|
|
|
|
RemoteId: proc.Remote.RemoteId,
|
|
|
|
RemoteAlias: proc.Remote.RemoteAlias,
|
|
|
|
RemoteCanonicalName: proc.Remote.RemoteCanonicalName,
|
|
|
|
PhysicalId: proc.Remote.PhysicalId,
|
|
|
|
Status: proc.Status,
|
|
|
|
ConnectMode: proc.Remote.ConnectMode,
|
|
|
|
}
|
|
|
|
if proc.Err != nil {
|
|
|
|
state.ErrorStr = proc.Err.Error()
|
|
|
|
}
|
2022-08-24 11:14:16 +02:00
|
|
|
local := (proc.Remote.SSHOpts == nil || proc.Remote.SSHOpts.Local)
|
2022-08-23 23:01:52 +02:00
|
|
|
vars := make(map[string]string)
|
|
|
|
vars["user"] = proc.Remote.RemoteUser
|
2022-08-24 11:14:16 +02:00
|
|
|
vars["bestuser"] = vars["user"]
|
2022-08-23 23:01:52 +02:00
|
|
|
vars["host"] = proc.Remote.RemoteHost
|
2022-08-24 06:05:49 +02:00
|
|
|
vars["shorthost"] = makeShortHost(proc.Remote.RemoteHost)
|
2022-08-23 23:01:52 +02:00
|
|
|
vars["alias"] = proc.Remote.RemoteAlias
|
|
|
|
vars["cname"] = proc.Remote.RemoteCanonicalName
|
|
|
|
vars["physicalid"] = proc.Remote.PhysicalId
|
|
|
|
vars["remoteid"] = proc.Remote.RemoteId
|
|
|
|
vars["status"] = proc.Status
|
|
|
|
vars["type"] = proc.Remote.RemoteType
|
2022-08-24 11:14:16 +02:00
|
|
|
if proc.Remote.RemoteSudo {
|
|
|
|
vars["sudo"] = "1"
|
|
|
|
}
|
|
|
|
if local {
|
|
|
|
vars["local"] = "1"
|
|
|
|
}
|
2022-08-23 23:01:52 +02:00
|
|
|
if proc.ServerProc != nil && proc.ServerProc.InitPk != nil {
|
|
|
|
state.DefaultState = &sstore.RemoteState{
|
|
|
|
Cwd: proc.ServerProc.InitPk.Cwd,
|
|
|
|
Env0: proc.ServerProc.InitPk.Env0,
|
|
|
|
}
|
|
|
|
vars["home"] = proc.ServerProc.InitPk.HomeDir
|
|
|
|
vars["remoteuser"] = proc.ServerProc.InitPk.User
|
2022-08-24 11:14:16 +02:00
|
|
|
vars["bestuser"] = vars["remoteuser"]
|
2022-08-23 23:01:52 +02:00
|
|
|
vars["remotehost"] = proc.ServerProc.InitPk.HostName
|
2022-08-24 06:05:49 +02:00
|
|
|
vars["remoteshorthost"] = makeShortHost(proc.ServerProc.InitPk.HostName)
|
2022-08-24 11:14:16 +02:00
|
|
|
vars["besthost"] = vars["remotehost"]
|
|
|
|
vars["bestshorthost"] = vars["remoteshorthost"]
|
|
|
|
}
|
|
|
|
if local && proc.Remote.RemoteSudo {
|
|
|
|
vars["bestuser"] = "sudo"
|
|
|
|
} else if proc.Remote.RemoteSudo {
|
|
|
|
vars["bestuser"] = "sudo@" + vars["bestuser"]
|
|
|
|
}
|
|
|
|
if local {
|
|
|
|
vars["bestname"] = vars["bestuser"] + "@local"
|
|
|
|
vars["bestshortname"] = vars["bestuser"] + "@local"
|
|
|
|
} else {
|
|
|
|
vars["bestname"] = vars["bestuser"] + "@" + vars["besthost"]
|
|
|
|
vars["bestshortname"] = vars["bestuser"] + "@" + vars["bestshorthost"]
|
2022-08-23 23:01:52 +02:00
|
|
|
}
|
|
|
|
state.RemoteVars = vars
|
|
|
|
return state
|
|
|
|
}
|
|
|
|
|
2022-08-26 22:12:17 +02:00
|
|
|
func (msh *MShellProc) NotifyUpdate() {
|
2022-08-30 01:31:06 +02:00
|
|
|
rstate := msh.GetRemoteRuntimeState()
|
2022-08-26 22:12:17 +02:00
|
|
|
update := &sstore.ModelUpdate{Remote: rstate}
|
|
|
|
sstore.MainBus.SendUpdate("", update)
|
|
|
|
}
|
|
|
|
|
2022-08-30 01:31:06 +02:00
|
|
|
func GetAllRemoteRuntimeState() []RemoteRuntimeState {
|
2022-07-05 07:18:01 +02:00
|
|
|
GlobalStore.Lock.Lock()
|
|
|
|
defer GlobalStore.Lock.Unlock()
|
|
|
|
|
2022-08-30 01:31:06 +02:00
|
|
|
var rtn []RemoteRuntimeState
|
2022-07-05 07:18:01 +02:00
|
|
|
for _, proc := range GlobalStore.Map {
|
2022-08-30 01:31:06 +02:00
|
|
|
state := proc.GetRemoteRuntimeState()
|
2022-07-05 07:18:01 +02:00
|
|
|
rtn = append(rtn, state)
|
|
|
|
}
|
|
|
|
return rtn
|
|
|
|
}
|
|
|
|
|
2022-07-16 02:37:32 +02:00
|
|
|
func GetDefaultRemoteStateById(remoteId string) (*sstore.RemoteState, error) {
|
|
|
|
remote := GetRemoteById(remoteId)
|
|
|
|
if remote == nil {
|
|
|
|
return nil, fmt.Errorf("remote not found")
|
|
|
|
}
|
|
|
|
if !remote.IsConnected() {
|
|
|
|
return nil, fmt.Errorf("remote not connected")
|
|
|
|
}
|
|
|
|
state := remote.GetDefaultState()
|
|
|
|
if state == nil {
|
|
|
|
return nil, fmt.Errorf("could not get default remote state")
|
|
|
|
}
|
|
|
|
return state, nil
|
|
|
|
}
|
|
|
|
|
2022-07-02 02:38:36 +02:00
|
|
|
func MakeMShell(r *sstore.RemoteType) *MShellProc {
|
|
|
|
rtn := &MShellProc{Lock: &sync.Mutex{}, Remote: r, Status: StatusInit}
|
|
|
|
return rtn
|
2022-07-01 23:57:42 +02:00
|
|
|
}
|
|
|
|
|
2022-08-17 21:24:09 +02:00
|
|
|
func convertSSHOpts(opts *sstore.SSHOpts) shexec.SSHOpts {
|
2022-08-24 11:14:16 +02:00
|
|
|
if opts == nil || opts.Local {
|
|
|
|
opts = &sstore.SSHOpts{}
|
2022-08-17 21:24:09 +02:00
|
|
|
}
|
|
|
|
return shexec.SSHOpts{
|
|
|
|
SSHHost: opts.SSHHost,
|
|
|
|
SSHOptsStr: opts.SSHOptsStr,
|
|
|
|
SSHIdentity: opts.SSHIdentity,
|
|
|
|
SSHUser: opts.SSHUser,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-25 07:57:41 +02:00
|
|
|
func (msh *MShellProc) addControllingTty(ecmd *exec.Cmd) (*os.File, error) {
|
|
|
|
msh.Lock.Lock()
|
|
|
|
defer msh.Lock.Unlock()
|
|
|
|
|
2022-08-19 22:23:00 +02:00
|
|
|
cmdPty, cmdTty, err := pty.Open()
|
|
|
|
if err != nil {
|
2022-08-25 07:57:41 +02:00
|
|
|
return nil, err
|
2022-08-19 22:23:00 +02:00
|
|
|
}
|
|
|
|
msh.ControllingPty = cmdPty
|
|
|
|
ecmd.ExtraFiles = append(ecmd.ExtraFiles, cmdTty)
|
|
|
|
if ecmd.SysProcAttr == nil {
|
|
|
|
ecmd.SysProcAttr = &syscall.SysProcAttr{}
|
|
|
|
}
|
|
|
|
ecmd.SysProcAttr.Setsid = true
|
|
|
|
ecmd.SysProcAttr.Setctty = true
|
|
|
|
ecmd.SysProcAttr.Ctty = len(ecmd.ExtraFiles) + 3 - 1
|
2022-08-25 07:57:41 +02:00
|
|
|
return cmdPty, nil
|
2022-08-19 22:23:00 +02:00
|
|
|
}
|
|
|
|
|
2022-08-25 07:57:41 +02:00
|
|
|
func (msh *MShellProc) setErrorStatus(err error) {
|
2022-07-02 02:38:36 +02:00
|
|
|
msh.Lock.Lock()
|
|
|
|
defer msh.Lock.Unlock()
|
2022-08-25 07:57:41 +02:00
|
|
|
msh.Status = StatusError
|
|
|
|
msh.Err = err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (msh *MShellProc) getRemoteCopy() sstore.RemoteType {
|
|
|
|
msh.Lock.Lock()
|
|
|
|
defer msh.Lock.Unlock()
|
|
|
|
return *msh.Remote
|
|
|
|
}
|
2022-07-02 02:38:36 +02:00
|
|
|
|
2022-08-25 07:57:41 +02:00
|
|
|
func (msh *MShellProc) Launch() {
|
|
|
|
remote := msh.getRemoteCopy()
|
|
|
|
ecmd := convertSSHOpts(remote.SSHOpts).MakeSSHExecCmd(MShellServerCommand)
|
|
|
|
cmdPty, err := msh.addControllingTty(ecmd)
|
2022-08-19 22:23:00 +02:00
|
|
|
if err != nil {
|
2022-08-25 07:57:41 +02:00
|
|
|
msh.setErrorStatus(fmt.Errorf("cannot attach controlling tty to mshell command: %w", err))
|
2022-08-19 22:23:00 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if len(ecmd.ExtraFiles) > 0 {
|
|
|
|
ecmd.ExtraFiles[len(ecmd.ExtraFiles)-1].Close()
|
|
|
|
}
|
|
|
|
}()
|
2022-08-25 07:57:41 +02:00
|
|
|
remoteName := remote.GetName()
|
2022-08-19 22:23:00 +02:00
|
|
|
go func() {
|
2022-08-25 07:57:41 +02:00
|
|
|
fmt.Printf("[c-pty %s] starting...\n", remote.GetName())
|
2022-08-19 22:23:00 +02:00
|
|
|
buf := make([]byte, 100)
|
|
|
|
for {
|
2022-08-25 07:57:41 +02:00
|
|
|
n, readErr := cmdPty.Read(buf)
|
2022-08-19 22:23:00 +02:00
|
|
|
if readErr == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if readErr != nil {
|
|
|
|
fmt.Printf("[error] read from controlling-pty [%s]: %v\n", remoteName, readErr)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
readStr := string(buf[0:n])
|
|
|
|
readStr = strings.ReplaceAll(readStr, "\r", "")
|
|
|
|
readStr = strings.ReplaceAll(readStr, "\n", "\\n")
|
|
|
|
fmt.Printf("[c-pty %s] %d '%s'\n", remoteName, n, readStr)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
if remoteName == "test2" {
|
|
|
|
go func() {
|
|
|
|
time.Sleep(2 * time.Second)
|
2022-08-25 07:57:41 +02:00
|
|
|
cmdPty.Write([]byte(Test2Pw))
|
2022-08-19 22:23:00 +02:00
|
|
|
fmt.Printf("[c-pty %s] wrote password!\n", remoteName)
|
|
|
|
}()
|
|
|
|
}
|
2022-08-17 21:54:40 +02:00
|
|
|
cproc, uname, err := shexec.MakeClientProc(ecmd)
|
2022-08-25 07:57:41 +02:00
|
|
|
msh.WithLock(func() {
|
|
|
|
msh.UName = uname
|
|
|
|
})
|
2022-07-02 02:38:36 +02:00
|
|
|
if err != nil {
|
2022-08-25 07:57:41 +02:00
|
|
|
msh.setErrorStatus(err)
|
2022-08-19 22:23:00 +02:00
|
|
|
fmt.Printf("[error] connecting remote %s (%s): %v\n", msh.Remote.GetName(), msh.UName, err)
|
2022-07-02 02:38:36 +02:00
|
|
|
return
|
2022-07-01 21:17:19 +02:00
|
|
|
}
|
2022-08-17 21:54:40 +02:00
|
|
|
fmt.Printf("connected remote %s\n", msh.Remote.GetName())
|
2022-08-25 07:57:41 +02:00
|
|
|
msh.WithLock(func() {
|
|
|
|
msh.ServerProc = cproc
|
|
|
|
msh.Status = StatusConnected
|
|
|
|
})
|
2022-07-01 21:17:19 +02:00
|
|
|
go func() {
|
2022-07-07 04:01:00 +02:00
|
|
|
exitErr := cproc.Cmd.Wait()
|
2022-07-01 21:17:19 +02:00
|
|
|
exitCode := shexec.GetExitCode(exitErr)
|
2022-07-02 02:38:36 +02:00
|
|
|
msh.WithLock(func() {
|
2022-07-07 04:01:00 +02:00
|
|
|
if msh.Status == StatusConnected {
|
2022-07-02 02:38:36 +02:00
|
|
|
msh.Status = StatusDisconnected
|
|
|
|
}
|
|
|
|
})
|
2022-07-01 21:17:19 +02:00
|
|
|
fmt.Printf("[error] RUNNER PROC EXITED code[%d]\n", exitCode)
|
|
|
|
}()
|
2022-07-02 02:38:36 +02:00
|
|
|
go msh.ProcessPackets()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (msh *MShellProc) IsConnected() bool {
|
|
|
|
msh.Lock.Lock()
|
|
|
|
defer msh.Lock.Unlock()
|
|
|
|
return msh.Status == StatusConnected
|
2022-07-01 21:17:19 +02:00
|
|
|
}
|
|
|
|
|
2022-07-16 02:37:32 +02:00
|
|
|
func (msh *MShellProc) GetDefaultState() *sstore.RemoteState {
|
|
|
|
msh.Lock.Lock()
|
|
|
|
defer msh.Lock.Unlock()
|
|
|
|
if msh.ServerProc == nil || msh.ServerProc.InitPk == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2022-08-23 01:26:44 +02:00
|
|
|
return &sstore.RemoteState{Cwd: msh.ServerProc.InitPk.HomeDir, Env0: msh.ServerProc.InitPk.Env0}
|
2022-07-16 02:37:32 +02:00
|
|
|
}
|
|
|
|
|
2022-08-24 11:14:16 +02:00
|
|
|
func replaceHomePath(pathStr string, homeDir string) string {
|
|
|
|
if homeDir == "" {
|
|
|
|
return pathStr
|
|
|
|
}
|
|
|
|
if pathStr == homeDir {
|
|
|
|
return "~"
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(pathStr, homeDir+"/") {
|
|
|
|
return "~" + pathStr[len(homeDir):]
|
|
|
|
}
|
|
|
|
return pathStr
|
|
|
|
}
|
|
|
|
|
2022-08-30 01:31:06 +02:00
|
|
|
func (state RemoteRuntimeState) ExpandHomeDir(pathStr string) (string, error) {
|
2022-08-17 21:24:09 +02:00
|
|
|
if pathStr != "~" && !strings.HasPrefix(pathStr, "~/") {
|
|
|
|
return pathStr, nil
|
|
|
|
}
|
2022-08-24 22:21:54 +02:00
|
|
|
homeDir := state.RemoteVars["home"]
|
2022-08-17 21:24:09 +02:00
|
|
|
if homeDir == "" {
|
|
|
|
return "", fmt.Errorf("remote does not have HOME set, cannot do ~ expansion")
|
|
|
|
}
|
|
|
|
if pathStr == "~" {
|
|
|
|
return homeDir, nil
|
|
|
|
}
|
|
|
|
return path.Join(homeDir, pathStr[2:]), nil
|
|
|
|
}
|
|
|
|
|
2022-07-08 07:46:28 +02:00
|
|
|
func (msh *MShellProc) IsCmdRunning(ck base.CommandKey) bool {
|
|
|
|
msh.Lock.Lock()
|
|
|
|
defer msh.Lock.Unlock()
|
|
|
|
for _, runningCk := range msh.RunningCmds {
|
|
|
|
if runningCk == ck {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (msh *MShellProc) SendInput(pk *packet.InputPacketType) error {
|
|
|
|
if !msh.IsConnected() {
|
|
|
|
return fmt.Errorf("remote is not connected, cannot send input")
|
|
|
|
}
|
|
|
|
if !msh.IsCmdRunning(pk.CK) {
|
|
|
|
return fmt.Errorf("cannot send input, cmd is not running")
|
|
|
|
}
|
|
|
|
dataPk := packet.MakeDataPacket()
|
|
|
|
dataPk.CK = pk.CK
|
|
|
|
dataPk.FdNum = 0 // stdin
|
|
|
|
dataPk.Data64 = pk.InputData64
|
|
|
|
return msh.ServerProc.Input.SendPacket(dataPk)
|
|
|
|
}
|
|
|
|
|
2022-07-07 09:10:37 +02:00
|
|
|
func makeTermOpts() sstore.TermOpts {
|
2022-08-19 22:23:00 +02:00
|
|
|
return sstore.TermOpts{Rows: DefaultTermRows, Cols: DefaultTermCols, FlexRows: true, MaxPtySize: DefaultMaxPtySize}
|
2022-07-07 09:10:37 +02:00
|
|
|
}
|
|
|
|
|
2022-08-24 22:21:54 +02:00
|
|
|
func RunCommand(ctx context.Context, cmdId string, remotePtr sstore.RemotePtrType, remoteState *sstore.RemoteState, runPacket *packet.RunPacketType) (*sstore.CmdType, error) {
|
|
|
|
if remotePtr.OwnerId != "" {
|
|
|
|
return nil, fmt.Errorf("cannot run command against another user's remote '%s'", remotePtr.MakeFullRemoteRef())
|
|
|
|
}
|
|
|
|
msh := GetRemoteById(remotePtr.RemoteId)
|
2022-07-06 01:54:49 +02:00
|
|
|
if msh == nil {
|
2022-08-24 22:21:54 +02:00
|
|
|
return nil, fmt.Errorf("no remote id=%s found", remotePtr.RemoteId)
|
2022-07-06 01:54:49 +02:00
|
|
|
}
|
|
|
|
if !msh.IsConnected() {
|
2022-08-24 22:21:54 +02:00
|
|
|
return nil, fmt.Errorf("remote '%s' is not connected", remotePtr.RemoteId)
|
2022-07-16 02:37:32 +02:00
|
|
|
}
|
|
|
|
if remoteState == nil {
|
|
|
|
return nil, fmt.Errorf("no remote state passed to RunCommand")
|
2022-07-06 01:54:49 +02:00
|
|
|
}
|
2022-07-07 04:01:00 +02:00
|
|
|
msh.ServerProc.Output.RegisterRpc(runPacket.ReqId)
|
|
|
|
err := shexec.SendRunPacketAndRunData(ctx, msh.ServerProc.Input, runPacket)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("sending run packet to remote: %w", err)
|
|
|
|
}
|
|
|
|
rtnPk := msh.ServerProc.Output.WaitForResponse(ctx, runPacket.ReqId)
|
2022-08-31 21:01:16 +02:00
|
|
|
if rtnPk == nil {
|
|
|
|
return nil, ctx.Err()
|
|
|
|
}
|
2022-07-07 09:10:37 +02:00
|
|
|
startPk, ok := rtnPk.(*packet.CmdStartPacketType)
|
|
|
|
if !ok {
|
|
|
|
respPk, ok := rtnPk.(*packet.ResponsePacketType)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("invalid response received from server for run packet: %s", packet.AsString(rtnPk))
|
|
|
|
}
|
2022-07-07 04:01:00 +02:00
|
|
|
if respPk.Error != "" {
|
|
|
|
return nil, errors.New(respPk.Error)
|
|
|
|
}
|
2022-07-07 09:10:37 +02:00
|
|
|
return nil, fmt.Errorf("invalid response received from server for run packet: %s", packet.AsString(rtnPk))
|
|
|
|
}
|
2022-07-08 01:29:14 +02:00
|
|
|
status := sstore.CmdStatusRunning
|
|
|
|
if runPacket.Detached {
|
|
|
|
status = sstore.CmdStatusDetached
|
|
|
|
}
|
2022-07-07 09:10:37 +02:00
|
|
|
cmd := &sstore.CmdType{
|
2022-07-16 02:37:32 +02:00
|
|
|
SessionId: runPacket.CK.GetSessionId(),
|
2022-07-07 09:10:37 +02:00
|
|
|
CmdId: startPk.CK.GetCmdId(),
|
2022-07-07 22:26:46 +02:00
|
|
|
CmdStr: runPacket.Command,
|
2022-08-24 22:21:54 +02:00
|
|
|
Remote: remotePtr,
|
2022-07-16 02:37:32 +02:00
|
|
|
RemoteState: *remoteState,
|
2022-07-07 09:10:37 +02:00
|
|
|
TermOpts: makeTermOpts(),
|
2022-07-08 01:29:14 +02:00
|
|
|
Status: status,
|
2022-07-07 09:10:37 +02:00
|
|
|
StartPk: startPk,
|
|
|
|
DonePk: nil,
|
|
|
|
RunOut: nil,
|
2022-07-07 04:01:00 +02:00
|
|
|
}
|
2022-08-19 22:23:00 +02:00
|
|
|
err = sstore.CreateCmdPtyFile(ctx, cmd.SessionId, cmd.CmdId, cmd.TermOpts.MaxPtySize)
|
2022-07-08 06:39:25 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-07-08 07:13:45 +02:00
|
|
|
msh.AddRunningCmd(startPk.CK)
|
2022-07-07 09:10:37 +02:00
|
|
|
return cmd, nil
|
2022-07-06 01:54:49 +02:00
|
|
|
}
|
|
|
|
|
2022-07-08 07:13:45 +02:00
|
|
|
func (msh *MShellProc) AddRunningCmd(ck base.CommandKey) {
|
|
|
|
msh.Lock.Lock()
|
|
|
|
defer msh.Lock.Unlock()
|
|
|
|
msh.RunningCmds = append(msh.RunningCmds, ck)
|
|
|
|
}
|
|
|
|
|
2022-07-07 04:01:00 +02:00
|
|
|
func (msh *MShellProc) PacketRpc(ctx context.Context, pk packet.RpcPacketType) (*packet.ResponsePacketType, error) {
|
|
|
|
if !msh.IsConnected() {
|
2022-07-02 02:38:36 +02:00
|
|
|
return nil, fmt.Errorf("runner is not connected")
|
|
|
|
}
|
2022-07-01 21:17:19 +02:00
|
|
|
if pk == nil {
|
|
|
|
return nil, fmt.Errorf("PacketRpc passed nil packet")
|
|
|
|
}
|
2022-07-07 04:01:00 +02:00
|
|
|
reqId := pk.GetReqId()
|
|
|
|
msh.ServerProc.Output.RegisterRpc(reqId)
|
|
|
|
defer msh.ServerProc.Output.UnRegisterRpc(reqId)
|
|
|
|
err := msh.ServerProc.Input.SendPacketCtx(ctx, pk)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
rtnPk := msh.ServerProc.Output.WaitForResponse(ctx, reqId)
|
|
|
|
if rtnPk == nil {
|
|
|
|
return nil, ctx.Err()
|
2022-07-01 21:17:19 +02:00
|
|
|
}
|
2022-07-07 04:01:00 +02:00
|
|
|
if respPk, ok := rtnPk.(*packet.ResponsePacketType); ok {
|
|
|
|
return respPk, nil
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("invalid response packet received: %s", packet.AsString(rtnPk))
|
2022-07-01 21:17:19 +02:00
|
|
|
}
|
|
|
|
|
2022-07-02 02:38:36 +02:00
|
|
|
func (runner *MShellProc) WithLock(fn func()) {
|
|
|
|
runner.Lock.Lock()
|
|
|
|
defer runner.Lock.Unlock()
|
|
|
|
fn()
|
|
|
|
}
|
|
|
|
|
2022-07-07 07:46:22 +02:00
|
|
|
func makeDataAckPacket(ck base.CommandKey, fdNum int, ackLen int, err error) *packet.DataAckPacketType {
|
|
|
|
ack := packet.MakeDataAckPacket()
|
|
|
|
ack.CK = ck
|
|
|
|
ack.FdNum = fdNum
|
|
|
|
ack.AckLen = ackLen
|
|
|
|
if err != nil {
|
|
|
|
ack.Error = err.Error()
|
|
|
|
}
|
|
|
|
return ack
|
|
|
|
}
|
|
|
|
|
2022-07-08 01:29:14 +02:00
|
|
|
func (msh *MShellProc) handleCmdDonePacket(donePk *packet.CmdDonePacketType) {
|
2022-08-20 02:14:53 +02:00
|
|
|
update, err := sstore.UpdateCmdDonePk(context.Background(), donePk)
|
2022-07-08 01:29:14 +02:00
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("[error] updating cmddone: %v\n", err)
|
|
|
|
return
|
|
|
|
}
|
2022-08-20 02:14:53 +02:00
|
|
|
if update != nil {
|
|
|
|
// TODO fix timing issue (this update gets to the FE before run-command returns for short lived commands)
|
|
|
|
go func() {
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
sstore.MainBus.SendUpdate(donePk.CK.GetSessionId(), update)
|
|
|
|
}()
|
2022-07-08 07:13:45 +02:00
|
|
|
}
|
2022-07-08 01:29:14 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (msh *MShellProc) handleCmdErrorPacket(errPk *packet.CmdErrorPacketType) {
|
|
|
|
err := sstore.AppendCmdErrorPk(context.Background(), errPk)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("[error] adding cmderr: %v\n", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-07-08 07:13:45 +02:00
|
|
|
func (msh *MShellProc) notifyHangups_nolock() {
|
2022-08-20 02:14:53 +02:00
|
|
|
for _, ck := range msh.RunningCmds {
|
|
|
|
cmd, err := sstore.GetCmdById(context.Background(), ck.GetSessionId(), ck.GetCmdId())
|
|
|
|
if err != nil {
|
|
|
|
continue
|
2022-07-08 07:13:45 +02:00
|
|
|
}
|
2022-08-24 11:14:16 +02:00
|
|
|
update := sstore.ModelUpdate{Cmd: cmd}
|
2022-08-20 02:14:53 +02:00
|
|
|
sstore.MainBus.SendUpdate(ck.GetSessionId(), update)
|
2022-07-08 07:13:45 +02:00
|
|
|
}
|
|
|
|
msh.RunningCmds = nil
|
|
|
|
}
|
|
|
|
|
2022-07-01 21:17:19 +02:00
|
|
|
func (runner *MShellProc) ProcessPackets() {
|
2022-07-02 02:38:36 +02:00
|
|
|
defer runner.WithLock(func() {
|
2022-07-07 04:01:00 +02:00
|
|
|
if runner.Status == StatusConnected {
|
2022-07-02 02:38:36 +02:00
|
|
|
runner.Status = StatusDisconnected
|
|
|
|
}
|
2022-07-08 01:29:14 +02:00
|
|
|
err := sstore.HangupRunningCmdsByRemoteId(context.Background(), runner.Remote.RemoteId)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("[error] calling HUP on remoteid=%d cmds\n", runner.Remote.RemoteId)
|
|
|
|
}
|
2022-07-08 07:13:45 +02:00
|
|
|
runner.notifyHangups_nolock()
|
2022-07-02 02:38:36 +02:00
|
|
|
})
|
2022-08-13 19:27:22 +02:00
|
|
|
dataPosMap := make(map[base.CommandKey]int64)
|
2022-07-07 04:01:00 +02:00
|
|
|
for pk := range runner.ServerProc.Output.MainCh {
|
|
|
|
if pk.GetType() == packet.DataPacketStr {
|
2022-07-07 07:46:22 +02:00
|
|
|
dataPk := pk.(*packet.DataPacketType)
|
|
|
|
realData, err := base64.StdEncoding.DecodeString(dataPk.Data64)
|
|
|
|
if err != nil {
|
|
|
|
ack := makeDataAckPacket(dataPk.CK, dataPk.FdNum, 0, err)
|
|
|
|
runner.ServerProc.Input.SendPacket(ack)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
var ack *packet.DataAckPacketType
|
|
|
|
if len(realData) > 0 {
|
2022-08-13 19:27:22 +02:00
|
|
|
dataPos := dataPosMap[dataPk.CK]
|
2022-08-12 22:59:31 +02:00
|
|
|
err = sstore.AppendToCmdPtyBlob(context.Background(), dataPk.CK.GetSessionId(), dataPk.CK.GetCmdId(), realData, dataPos)
|
2022-07-07 07:46:22 +02:00
|
|
|
if err != nil {
|
|
|
|
ack = makeDataAckPacket(dataPk.CK, dataPk.FdNum, 0, err)
|
|
|
|
} else {
|
|
|
|
ack = makeDataAckPacket(dataPk.CK, dataPk.FdNum, len(realData), nil)
|
|
|
|
}
|
2022-08-13 19:27:22 +02:00
|
|
|
dataPosMap[dataPk.CK] += int64(len(realData))
|
2022-07-07 07:46:22 +02:00
|
|
|
}
|
|
|
|
if ack != nil {
|
|
|
|
runner.ServerProc.Input.SendPacket(ack)
|
|
|
|
}
|
2022-07-08 06:39:25 +02:00
|
|
|
// fmt.Printf("data %s fd=%d len=%d eof=%v err=%v\n", dataPk.CK, dataPk.FdNum, len(realData), dataPk.Eof, dataPk.Error)
|
2022-07-07 04:01:00 +02:00
|
|
|
continue
|
2022-07-01 21:17:19 +02:00
|
|
|
}
|
|
|
|
if pk.GetType() == packet.CmdDataPacketStr {
|
|
|
|
dataPacket := pk.(*packet.CmdDataPacketType)
|
2022-07-07 04:01:00 +02:00
|
|
|
fmt.Printf("cmd-data %s pty=%d run=%d\n", dataPacket.CK, dataPacket.PtyDataLen, dataPacket.RunDataLen)
|
2022-07-01 21:17:19 +02:00
|
|
|
continue
|
|
|
|
}
|
2022-07-07 04:01:00 +02:00
|
|
|
if pk.GetType() == packet.CmdDonePacketStr {
|
2022-07-08 01:29:14 +02:00
|
|
|
runner.handleCmdDonePacket(pk.(*packet.CmdDonePacketType))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if pk.GetType() == packet.CmdErrorPacketStr {
|
|
|
|
runner.handleCmdErrorPacket(pk.(*packet.CmdErrorPacketType))
|
2022-07-01 21:17:19 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
if pk.GetType() == packet.MessagePacketStr {
|
|
|
|
msgPacket := pk.(*packet.MessagePacketType)
|
|
|
|
fmt.Printf("# %s\n", msgPacket.Message)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if pk.GetType() == packet.RawPacketStr {
|
|
|
|
rawPacket := pk.(*packet.RawPacketType)
|
|
|
|
fmt.Printf("stderr> %s\n", rawPacket.Data)
|
|
|
|
continue
|
|
|
|
}
|
2022-07-07 07:46:22 +02:00
|
|
|
if pk.GetType() == packet.CmdStartPacketStr {
|
|
|
|
startPk := pk.(*packet.CmdStartPacketType)
|
|
|
|
fmt.Printf("start> reqid=%s (%p)\n", startPk.RespId, runner.ServerProc.Output)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
fmt.Printf("MSH> %s\n", packet.AsString(pk))
|
2022-07-01 21:17:19 +02:00
|
|
|
}
|
|
|
|
}
|
2022-08-24 06:05:49 +02:00
|
|
|
|
2022-08-24 11:14:16 +02:00
|
|
|
// returns number of chars (including braces) for brace-expr
|
|
|
|
func getBracedStr(runeStr []rune) int {
|
|
|
|
if len(runeStr) < 3 {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
if runeStr[0] != '{' {
|
|
|
|
return 0
|
2022-08-24 06:05:49 +02:00
|
|
|
}
|
2022-08-24 11:14:16 +02:00
|
|
|
for i := 1; i < len(runeStr); i++ {
|
|
|
|
if runeStr[i] == '}' {
|
|
|
|
if i == 1 { // cannot have {}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return i + 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func isDigit(r rune) bool {
|
|
|
|
return r >= '0' && r <= '9' // just check ascii digits (not unicode)
|
|
|
|
}
|
|
|
|
|
|
|
|
func EvalPrompt(promptFmt string, vars map[string]string, state *sstore.RemoteState) string {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
promptRunes := []rune(promptFmt)
|
|
|
|
for i := 0; i < len(promptRunes); i++ {
|
|
|
|
ch := promptRunes[i]
|
|
|
|
if ch == '\\' && i != len(promptRunes)-1 {
|
|
|
|
nextCh := promptRunes[i+1]
|
|
|
|
if nextCh == 'x' || nextCh == 'y' {
|
|
|
|
nr := getBracedStr(promptRunes[i+2:])
|
|
|
|
if nr > 0 {
|
|
|
|
escCode := string(promptRunes[i+1 : i+1+nr+1]) // start at "x" or "y", extend nr+1 runes
|
|
|
|
escStr := evalPromptEsc(escCode, vars, state)
|
|
|
|
buf.WriteString(escStr)
|
|
|
|
i += nr + 1
|
|
|
|
continue
|
|
|
|
} else {
|
|
|
|
buf.WriteRune(ch) // invalid escape, so just write ch and move on
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
} else if isDigit(nextCh) {
|
|
|
|
if len(promptRunes) >= i+4 && isDigit(promptRunes[i+2]) && isDigit(promptRunes[i+3]) {
|
|
|
|
i += 3
|
|
|
|
escStr := evalPromptEsc(string(promptRunes[i+1:i+4]), vars, state)
|
|
|
|
buf.WriteString(escStr)
|
|
|
|
continue
|
|
|
|
} else {
|
|
|
|
buf.WriteRune(ch) // invalid escape, so just write ch and move on
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
i += 1
|
|
|
|
escStr := evalPromptEsc(string(nextCh), vars, state)
|
|
|
|
buf.WriteString(escStr)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
buf.WriteRune(ch)
|
|
|
|
}
|
|
|
|
return buf.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
func evalPromptEsc(escCode string, vars map[string]string, state *sstore.RemoteState) string {
|
2022-08-24 06:05:49 +02:00
|
|
|
if strings.HasPrefix(escCode, "x{") && strings.HasSuffix(escCode, "}") {
|
|
|
|
varName := escCode[2 : len(escCode)-1]
|
|
|
|
return vars[varName]
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(escCode, "y{") && strings.HasSuffix(escCode, "}") {
|
2022-08-24 11:14:16 +02:00
|
|
|
if state == nil {
|
|
|
|
return ""
|
|
|
|
}
|
2022-08-24 06:05:49 +02:00
|
|
|
varName := escCode[2 : len(escCode)-1]
|
|
|
|
varMap := shexec.ParseEnv0(state.Env0)
|
|
|
|
return varMap[varName]
|
|
|
|
}
|
|
|
|
if escCode == "h" {
|
|
|
|
return vars["remoteshorthost"]
|
|
|
|
}
|
|
|
|
if escCode == "H" {
|
|
|
|
return vars["remotehost"]
|
|
|
|
}
|
|
|
|
if escCode == "s" {
|
|
|
|
return "mshell"
|
|
|
|
}
|
|
|
|
if escCode == "u" {
|
|
|
|
return vars["remoteuser"]
|
|
|
|
}
|
|
|
|
if escCode == "w" {
|
2022-08-24 11:14:16 +02:00
|
|
|
if state == nil {
|
|
|
|
return "?"
|
|
|
|
}
|
|
|
|
return replaceHomePath(state.Cwd, vars["home"])
|
2022-08-24 06:05:49 +02:00
|
|
|
}
|
|
|
|
if escCode == "W" {
|
2022-08-24 11:14:16 +02:00
|
|
|
if state == nil {
|
|
|
|
return "?"
|
|
|
|
}
|
|
|
|
return path.Base(replaceHomePath(state.Cwd, vars["home"]))
|
2022-08-24 06:05:49 +02:00
|
|
|
}
|
|
|
|
if escCode == "$" {
|
2022-08-24 11:14:16 +02:00
|
|
|
if vars["remoteuser"] == "root" || vars["sudo"] == "1" {
|
2022-08-24 06:05:49 +02:00
|
|
|
return "#"
|
|
|
|
} else {
|
|
|
|
return "$"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(escCode) == 3 {
|
|
|
|
// \nnn escape
|
|
|
|
ival, err := strconv.ParseInt(escCode, 8, 32)
|
|
|
|
if err != nil {
|
|
|
|
return escCode
|
|
|
|
}
|
|
|
|
return string([]byte{byte(ival)})
|
|
|
|
}
|
|
|
|
if escCode == "e" {
|
|
|
|
return "\033"
|
|
|
|
}
|
|
|
|
if escCode == "n" {
|
|
|
|
return "\n"
|
|
|
|
}
|
|
|
|
if escCode == "r" {
|
|
|
|
return "\r"
|
|
|
|
}
|
|
|
|
if escCode == "a" {
|
|
|
|
return "\007"
|
|
|
|
}
|
|
|
|
if escCode == "\\" {
|
|
|
|
return "\\"
|
|
|
|
}
|
|
|
|
if escCode == "[" {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
if escCode == "]" {
|
|
|
|
return ""
|
|
|
|
}
|
2022-08-24 11:14:16 +02:00
|
|
|
|
|
|
|
// we don't support date/time escapes (d, t, T, @), version escapes (v, V), cmd number (#, !), terminal device (l), jobs (j)
|
2022-08-24 06:05:49 +02:00
|
|
|
return "(" + escCode + ")"
|
|
|
|
}
|