2022-07-01 21:17:19 +02:00
|
|
|
package remote
|
|
|
|
|
|
|
|
import (
|
2022-07-02 02:38:36 +02:00
|
|
|
"context"
|
2022-07-07 04:01:00 +02:00
|
|
|
"errors"
|
2022-07-01 21:17:19 +02:00
|
|
|
"fmt"
|
|
|
|
"os/exec"
|
2022-07-06 01:54:49 +02:00
|
|
|
"strings"
|
2022-07-01 21:17:19 +02:00
|
|
|
"sync"
|
|
|
|
|
2022-07-07 04:01:00 +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-06 01:54:49 +02:00
|
|
|
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
|
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-07-01 21:17:19 +02:00
|
|
|
|
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 {
|
|
|
|
Lock *sync.Mutex
|
2022-07-06 01:54:49 +02:00
|
|
|
Map map[string]*MShellProc // key=remoteid
|
2022-07-01 23:57:42 +02:00
|
|
|
}
|
|
|
|
|
2022-07-05 07:18:01 +02:00
|
|
|
type RemoteState struct {
|
2022-07-06 01:54:49 +02:00
|
|
|
RemoteType string `json:"remotetype"`
|
|
|
|
RemoteId string `json:"remoteid"`
|
|
|
|
RemoteName string `json:"remotename"`
|
|
|
|
Status string `json:"status"`
|
|
|
|
DefaultState *sstore.RemoteState `json:"defaultstate"`
|
2022-07-05 07:18:01 +02:00
|
|
|
}
|
|
|
|
|
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-07-07 04:01:00 +02:00
|
|
|
Status string
|
|
|
|
ServerProc *shexec.ClientProc
|
|
|
|
Err error
|
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-07-02 02:38:36 +02:00
|
|
|
if remote.AutoConnect {
|
|
|
|
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 {
|
|
|
|
if msh.Remote.RemoteName == name {
|
|
|
|
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-07-05 07:18:01 +02:00
|
|
|
func GetAllRemoteState() []RemoteState {
|
|
|
|
GlobalStore.Lock.Lock()
|
|
|
|
defer GlobalStore.Lock.Unlock()
|
|
|
|
|
|
|
|
var rtn []RemoteState
|
|
|
|
for _, proc := range GlobalStore.Map {
|
|
|
|
state := RemoteState{
|
|
|
|
RemoteType: proc.Remote.RemoteType,
|
|
|
|
RemoteId: proc.Remote.RemoteId,
|
|
|
|
RemoteName: proc.Remote.RemoteName,
|
|
|
|
Status: proc.Status,
|
|
|
|
}
|
2022-07-07 04:01:00 +02:00
|
|
|
if proc.ServerProc != nil && proc.ServerProc.InitPk != nil {
|
|
|
|
state.DefaultState = &sstore.RemoteState{Cwd: proc.ServerProc.InitPk.HomeDir}
|
2022-07-05 07:18:01 +02:00
|
|
|
}
|
|
|
|
rtn = append(rtn, state)
|
|
|
|
}
|
|
|
|
return rtn
|
|
|
|
}
|
|
|
|
|
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-07-02 02:38:36 +02:00
|
|
|
func (msh *MShellProc) Launch() {
|
|
|
|
msh.Lock.Lock()
|
|
|
|
defer msh.Lock.Unlock()
|
|
|
|
|
2022-07-01 21:17:19 +02:00
|
|
|
msPath, err := base.GetMShellPath()
|
|
|
|
if err != nil {
|
2022-07-02 02:38:36 +02:00
|
|
|
msh.Status = StatusError
|
|
|
|
msh.Err = err
|
|
|
|
return
|
2022-07-01 21:17:19 +02:00
|
|
|
}
|
2022-07-02 02:38:36 +02:00
|
|
|
ecmd := exec.Command(msPath, "--server")
|
2022-07-07 04:01:00 +02:00
|
|
|
cproc, err := shexec.MakeClientProc(ecmd)
|
2022-07-02 02:38:36 +02:00
|
|
|
if err != nil {
|
|
|
|
msh.Status = StatusError
|
2022-07-07 04:01:00 +02:00
|
|
|
msh.Err = err
|
2022-07-02 02:38:36 +02:00
|
|
|
return
|
2022-07-01 21:17:19 +02:00
|
|
|
}
|
2022-07-07 04:01:00 +02:00
|
|
|
msh.ServerProc = cproc
|
|
|
|
fmt.Printf("START MAKECLIENTPROC: %#v\n", msh.ServerProc.InitPk)
|
|
|
|
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-07 04:01:00 +02:00
|
|
|
func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType, cmdId string) (*packet.CmdStartPacketType, error) {
|
2022-07-06 01:54:49 +02:00
|
|
|
msh := GetRemoteById(pk.RemoteState.RemoteId)
|
|
|
|
if msh == nil {
|
2022-07-07 04:01:00 +02:00
|
|
|
return nil, fmt.Errorf("no remote id=%s found", pk.RemoteState.RemoteId)
|
2022-07-06 01:54:49 +02:00
|
|
|
}
|
|
|
|
if !msh.IsConnected() {
|
2022-07-07 04:01:00 +02:00
|
|
|
return nil, fmt.Errorf("remote '%s' is not connected", msh.Remote.RemoteName)
|
2022-07-06 01:54:49 +02:00
|
|
|
}
|
|
|
|
runPacket := packet.MakeRunPacket()
|
2022-07-07 04:01:00 +02:00
|
|
|
runPacket.ReqId = uuid.New().String()
|
2022-07-06 01:54:49 +02:00
|
|
|
runPacket.CK = base.MakeCommandKey(pk.SessionId, cmdId)
|
|
|
|
runPacket.Cwd = pk.RemoteState.Cwd
|
|
|
|
runPacket.Env = nil
|
2022-07-07 04:01:00 +02:00
|
|
|
runPacket.UsePty = true
|
|
|
|
runPacket.TermOpts = &packet.TermOpts{Rows: DefaultTermRows, Cols: DefaultTermCols, Term: DefaultTerm}
|
2022-07-06 01:54:49 +02:00
|
|
|
runPacket.Command = strings.TrimSpace(pk.CmdStr)
|
2022-07-07 04:01:00 +02:00
|
|
|
fmt.Printf("RUN-CMD> %s\n", runPacket.CK)
|
|
|
|
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)
|
|
|
|
if startPk, ok := rtnPk.(*packet.CmdStartPacketType); ok {
|
|
|
|
return startPk, nil
|
|
|
|
}
|
|
|
|
if respPk, ok := rtnPk.(*packet.ResponsePacketType); ok {
|
|
|
|
if respPk.Error != "" {
|
|
|
|
return nil, errors.New(respPk.Error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("invalid response received from server for run packet: %s", packet.AsString(rtnPk))
|
2022-07-06 01:54:49 +02:00
|
|
|
}
|
|
|
|
|
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-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-07 04:01:00 +02:00
|
|
|
for pk := range runner.ServerProc.Output.MainCh {
|
|
|
|
fmt.Printf("MSH> %s | %#v\n", packet.AsString(pk), pk)
|
|
|
|
if pk.GetType() == packet.DataPacketStr {
|
|
|
|
dataPacket := pk.(*packet.DataPacketType)
|
|
|
|
fmt.Printf("data %s fd=%d len=%d eof=%v err=%v\n", dataPacket.CK, dataPacket.FdNum, packet.B64DecodedLen(dataPacket.Data64), dataPacket.Eof, dataPacket.Error)
|
|
|
|
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 {
|
|
|
|
donePacket := pk.(*packet.CmdDonePacketType)
|
|
|
|
fmt.Printf("cmd-done %s\n", donePacket.CK)
|
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
|
|
|
|
}
|
|
|
|
fmt.Printf("runner-packet: %v\n", pk)
|
|
|
|
}
|
|
|
|
}
|