mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-04 18:59:08 +01:00
create proof of concept ssh library integration
This is a first attempt to integrate the golang crypto/ssh library for handling remote connections. As it stands, this features is limited to identity files without passphrases. It needs to be expanded to include key+passphrase and password verifications as well.
This commit is contained in:
parent
1b81b2906c
commit
03ea444030
@ -651,7 +651,7 @@ func (m *MServer) runCommand(runPacket *packet.RunPacketType) {
|
|||||||
m.Sender.SendErrorResponse(runPacket.ReqId, fmt.Errorf("server run packets require valid ck: %s", err))
|
m.Sender.SendErrorResponse(runPacket.ReqId, fmt.Errorf("server run packets require valid ck: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cproc, _, err := shexec.MakeClientProc(context.Background(), ecmd)
|
cproc, _, err := shexec.MakeClientProc(context.Background(), shexec.CmdWrap{Cmd: ecmd})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.Sender.SendErrorResponse(runPacket.ReqId, fmt.Errorf("starting mshell client: %s", err))
|
m.Sender.SendErrorResponse(runPacket.ReqId, fmt.Errorf("starting mshell client: %s", err))
|
||||||
return
|
return
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/wavetermdev/waveterm/waveshell/pkg/base"
|
"github.com/wavetermdev/waveterm/waveshell/pkg/base"
|
||||||
"github.com/wavetermdev/waveterm/waveshell/pkg/packet"
|
"github.com/wavetermdev/waveterm/waveshell/pkg/packet"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
"golang.org/x/mod/semver"
|
"golang.org/x/mod/semver"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -19,8 +20,97 @@ import (
|
|||||||
|
|
||||||
const NotFoundVersion = "v0.0"
|
const NotFoundVersion = "v0.0"
|
||||||
|
|
||||||
|
type CmdWrap struct {
|
||||||
|
Cmd *exec.Cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cw CmdWrap) Kill() {
|
||||||
|
cw.Cmd.Process.Kill()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cw CmdWrap) Wait() error {
|
||||||
|
return cw.Cmd.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cw CmdWrap) Sender() (*packet.PacketSender, io.WriteCloser, error) {
|
||||||
|
inputWriter, err := cw.Cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("creating stdin pipe: %v", err)
|
||||||
|
}
|
||||||
|
sender := packet.MakePacketSender(inputWriter, nil)
|
||||||
|
return sender, inputWriter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cw CmdWrap) Parser() (*packet.PacketParser, io.ReadCloser, io.ReadCloser, error) {
|
||||||
|
stdoutReader, err := cw.Cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("creating stdout pipe: %v", err)
|
||||||
|
}
|
||||||
|
stderrReader, err := cw.Cmd.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("creating stderr pipe: %v", err)
|
||||||
|
}
|
||||||
|
stdoutPacketParser := packet.MakePacketParser(stdoutReader, &packet.PacketParserOpts{IgnoreUntilValid: true})
|
||||||
|
stderrPacketParser := packet.MakePacketParser(stderrReader, nil)
|
||||||
|
packetParser := packet.CombinePacketParsers(stdoutPacketParser, stderrPacketParser, true)
|
||||||
|
return packetParser, stdoutReader, stderrReader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cw CmdWrap) Start() error {
|
||||||
|
return cw.Cmd.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
type SessionWrap struct {
|
||||||
|
Session *ssh.Session
|
||||||
|
StartCmd string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sw SessionWrap) Kill() {
|
||||||
|
sw.Session.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sw SessionWrap) Wait() error {
|
||||||
|
return sw.Session.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sw SessionWrap) Start() error {
|
||||||
|
return sw.Session.Start(sw.StartCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sw SessionWrap) Sender() (*packet.PacketSender, io.WriteCloser, error) {
|
||||||
|
inputWriter, err := sw.Session.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("creating stdin pipe: %v", err)
|
||||||
|
}
|
||||||
|
sender := packet.MakePacketSender(inputWriter, nil)
|
||||||
|
return sender, inputWriter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sw SessionWrap) Parser() (*packet.PacketParser, io.ReadCloser, io.ReadCloser, error) {
|
||||||
|
stdoutReader, err := sw.Session.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("creating stdout pipe: %v", err)
|
||||||
|
}
|
||||||
|
stderrReader, err := sw.Session.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("creating stderr pipe: %v", err)
|
||||||
|
}
|
||||||
|
stdoutPacketParser := packet.MakePacketParser(stdoutReader, &packet.PacketParserOpts{IgnoreUntilValid: true})
|
||||||
|
stderrPacketParser := packet.MakePacketParser(stderrReader, nil)
|
||||||
|
packetParser := packet.CombinePacketParsers(stdoutPacketParser, stderrPacketParser, true)
|
||||||
|
return packetParser, io.NopCloser(stdoutReader), io.NopCloser(stderrReader), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnInterface interface {
|
||||||
|
Kill()
|
||||||
|
Wait() error
|
||||||
|
Sender() (*packet.PacketSender, io.WriteCloser, error)
|
||||||
|
Parser() (*packet.PacketParser, io.ReadCloser, io.ReadCloser, error)
|
||||||
|
Start() error
|
||||||
|
}
|
||||||
|
|
||||||
type ClientProc struct {
|
type ClientProc struct {
|
||||||
Cmd *exec.Cmd
|
Cmd ConnInterface
|
||||||
InitPk *packet.InitPacketType
|
InitPk *packet.InitPacketType
|
||||||
StartTs time.Time
|
StartTs time.Time
|
||||||
StdinWriter io.WriteCloser
|
StdinWriter io.WriteCloser
|
||||||
@ -31,28 +121,20 @@ type ClientProc struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// returns (clientproc, initpk, error)
|
// returns (clientproc, initpk, error)
|
||||||
func MakeClientProc(ctx context.Context, ecmd *exec.Cmd) (*ClientProc, *packet.InitPacketType, error) {
|
func MakeClientProc(ctx context.Context, ecmd ConnInterface) (*ClientProc, *packet.InitPacketType, error) {
|
||||||
inputWriter, err := ecmd.StdinPipe()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("creating stdin pipe: %v", err)
|
|
||||||
}
|
|
||||||
stdoutReader, err := ecmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("creating stdout pipe: %v", err)
|
|
||||||
}
|
|
||||||
stderrReader, err := ecmd.StderrPipe()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("creating stderr pipe: %v", err)
|
|
||||||
}
|
|
||||||
startTs := time.Now()
|
startTs := time.Now()
|
||||||
|
sender, inputWriter, err := ecmd.Sender()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
packetParser, stdoutReader, stderrReader, err := ecmd.Parser()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
err = ecmd.Start()
|
err = ecmd.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("running local client: %w", err)
|
return nil, nil, fmt.Errorf("running local client: %w", err)
|
||||||
}
|
}
|
||||||
sender := packet.MakePacketSender(inputWriter, nil)
|
|
||||||
stdoutPacketParser := packet.MakePacketParser(stdoutReader, &packet.PacketParserOpts{IgnoreUntilValid: true})
|
|
||||||
stderrPacketParser := packet.MakePacketParser(stderrReader, nil)
|
|
||||||
packetParser := packet.CombinePacketParsers(stdoutPacketParser, stderrPacketParser, true)
|
|
||||||
cproc := &ClientProc{
|
cproc := &ClientProc{
|
||||||
Cmd: ecmd,
|
Cmd: ecmd,
|
||||||
StartTs: startTs,
|
StartTs: startTs,
|
||||||
@ -107,7 +189,7 @@ func (cproc *ClientProc) Close() {
|
|||||||
cproc.StderrReader.Close()
|
cproc.StderrReader.Close()
|
||||||
}
|
}
|
||||||
if cproc.Cmd != nil {
|
if cproc.Cmd != nil {
|
||||||
cproc.Cmd.Process.Kill()
|
cproc.Cmd.Kill()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/alessio/shellescape"
|
"github.com/alessio/shellescape"
|
||||||
"github.com/creack/pty"
|
"github.com/creack/pty"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/kevinburke/ssh_config"
|
||||||
"github.com/wavetermdev/waveterm/waveshell/pkg/base"
|
"github.com/wavetermdev/waveterm/waveshell/pkg/base"
|
||||||
"github.com/wavetermdev/waveterm/waveshell/pkg/cirfile"
|
"github.com/wavetermdev/waveterm/waveshell/pkg/cirfile"
|
||||||
"github.com/wavetermdev/waveterm/waveshell/pkg/mpio"
|
"github.com/wavetermdev/waveterm/waveshell/pkg/mpio"
|
||||||
@ -31,6 +32,7 @@ import (
|
|||||||
"github.com/wavetermdev/waveterm/waveshell/pkg/shellapi"
|
"github.com/wavetermdev/waveterm/waveshell/pkg/shellapi"
|
||||||
"github.com/wavetermdev/waveterm/waveshell/pkg/shellenv"
|
"github.com/wavetermdev/waveterm/waveshell/pkg/shellenv"
|
||||||
"github.com/wavetermdev/waveterm/waveshell/pkg/shellutil"
|
"github.com/wavetermdev/waveterm/waveshell/pkg/shellutil"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
"golang.org/x/mod/semver"
|
"golang.org/x/mod/semver"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
@ -476,6 +478,76 @@ func (opts SSHOpts) MakeSSHExecCmd(remoteCommand string, sapi shellapi.ShellApi)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opts SSHOpts) ConnectToClient() (*ssh.Client, error) {
|
||||||
|
ssh_config.ReloadConfigs()
|
||||||
|
configIdentity, _ := ssh_config.GetStrict(opts.SSHHost, "IdentityFile")
|
||||||
|
var identityFile string
|
||||||
|
if opts.SSHIdentity != "" {
|
||||||
|
identityFile = opts.SSHIdentity
|
||||||
|
} else {
|
||||||
|
identityFile = configIdentity
|
||||||
|
}
|
||||||
|
|
||||||
|
var authMethods []ssh.AuthMethod
|
||||||
|
var hostKeyCallback ssh.HostKeyCallback
|
||||||
|
if identityFile != "" {
|
||||||
|
sshKeyFile, err := os.ReadFile(base.ExpandHomeDir(identityFile))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read ssh key file. err: %+v", err)
|
||||||
|
}
|
||||||
|
signer, err := ssh.ParsePrivateKey(sshKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse private ssh key. err: %+v", err)
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
publicKey, err := ssh.ParsePublicKey(sshKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse public ssh key. err: %+v", err)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
authMethods = append(authMethods, ssh.PublicKeys(signer))
|
||||||
|
hostKeyCallback = ssh.InsecureIgnoreHostKey()
|
||||||
|
} else {
|
||||||
|
hostKeyCallback = ssh.InsecureIgnoreHostKey()
|
||||||
|
}
|
||||||
|
configUser, _ := ssh_config.GetStrict(opts.SSHHost, "User")
|
||||||
|
configHostName, _ := ssh_config.GetStrict(opts.SSHHost, "HostName")
|
||||||
|
configPort, _ := ssh_config.GetStrict(opts.SSHHost, "Port")
|
||||||
|
var username string
|
||||||
|
if opts.SSHUser != "" {
|
||||||
|
username = opts.SSHUser
|
||||||
|
} else if configUser != "" {
|
||||||
|
username = configUser
|
||||||
|
} else {
|
||||||
|
user, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get user for ssh: %+v", err)
|
||||||
|
}
|
||||||
|
username = user.Username
|
||||||
|
}
|
||||||
|
var hostName string
|
||||||
|
if configHostName != "" {
|
||||||
|
hostName = configHostName
|
||||||
|
} else {
|
||||||
|
hostName = opts.SSHHost
|
||||||
|
}
|
||||||
|
clientConfig := &ssh.ClientConfig{
|
||||||
|
User: username,
|
||||||
|
Auth: authMethods,
|
||||||
|
HostKeyCallback: hostKeyCallback,
|
||||||
|
}
|
||||||
|
var port string
|
||||||
|
if opts.SSHPort != 0 && opts.SSHPort != 22 {
|
||||||
|
port = strconv.Itoa(opts.SSHPort)
|
||||||
|
} else if configPort != "" && configPort != "22" {
|
||||||
|
port = configPort
|
||||||
|
} else {
|
||||||
|
port = "22"
|
||||||
|
}
|
||||||
|
networkAddr := hostName + ":" + port
|
||||||
|
return ssh.Dial("tcp", networkAddr, clientConfig)
|
||||||
|
}
|
||||||
|
|
||||||
func (opts SSHOpts) MakeMShellSSHOpts() string {
|
func (opts SSHOpts) MakeMShellSSHOpts() string {
|
||||||
var moreSSHOpts []string
|
var moreSSHOpts []string
|
||||||
if opts.SSHIdentity != "" {
|
if opts.SSHIdentity != "" {
|
||||||
|
@ -36,6 +36,7 @@ import (
|
|||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbase"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbase"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket"
|
||||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/sstore"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
"golang.org/x/mod/semver"
|
"golang.org/x/mod/semver"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -77,6 +78,12 @@ else
|
|||||||
fi
|
fi
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const WaveshellServerRunOnlyFmt = `
|
||||||
|
PATH=$PATH:~/.mshell;
|
||||||
|
[%PINGPACKET%]
|
||||||
|
mshell-[%VERSION%] --server
|
||||||
|
`
|
||||||
|
|
||||||
func MakeLocalMShellCommandStr(isSudo bool) (string, error) {
|
func MakeLocalMShellCommandStr(isSudo bool) (string, error) {
|
||||||
mshellPath, err := scbase.LocalMShellBinaryPath()
|
mshellPath, err := scbase.LocalMShellBinaryPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -95,6 +102,13 @@ func MakeServerCommandStr() string {
|
|||||||
return rtn
|
return rtn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MakeServerRunOnlyCommandStr() string {
|
||||||
|
rtn := strings.ReplaceAll(WaveshellServerRunOnlyFmt, "[%VERSION%]", semver.MajorMinor(scbase.MShellVersion))
|
||||||
|
rtn = strings.ReplaceAll(rtn, "[%PINGPACKET%]", PrintPingPacket)
|
||||||
|
return rtn
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
StatusConnected = sstore.RemoteStatus_Connected
|
StatusConnected = sstore.RemoteStatus_Connected
|
||||||
StatusConnecting = sstore.RemoteStatus_Connecting
|
StatusConnecting = sstore.RemoteStatus_Connecting
|
||||||
@ -1206,7 +1220,286 @@ func (msh *MShellProc) getActiveShellTypes(ctx context.Context) ([]string, error
|
|||||||
return utilfn.CombineStrArrays(rtn, activeShells), nil
|
return utilfn.CombineStrArrays(rtn, activeShells), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (msh *MShellProc) LaunchWithSshLib(interactive bool) {
|
||||||
|
remoteCopy := msh.GetRemoteCopy()
|
||||||
|
if remoteCopy.Archived {
|
||||||
|
msh.WriteToPtyBuffer("cannot launch archived remote\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
curStatus := msh.GetStatus()
|
||||||
|
if curStatus == StatusConnected {
|
||||||
|
msh.WriteToPtyBuffer("remote is already connected (no action taken)\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if curStatus == StatusConnecting {
|
||||||
|
msh.WriteToPtyBuffer("remote is already connecting, disconnect before trying to connect again\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
istatus := msh.GetInstallStatus()
|
||||||
|
if istatus == StatusConnecting {
|
||||||
|
msh.WriteToPtyBuffer("remote is trying to install, cancel install before trying to connect again\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if remoteCopy.SSHOpts.SSHPort != 0 && remoteCopy.SSHOpts.SSHPort != 22 {
|
||||||
|
msh.WriteToPtyBuffer("connecting to %s (port %d)...\n", remoteCopy.RemoteCanonicalName, remoteCopy.SSHOpts.SSHPort)
|
||||||
|
} else {
|
||||||
|
msh.WriteToPtyBuffer("connecting to %s...\n", remoteCopy.RemoteCanonicalName)
|
||||||
|
}
|
||||||
|
sshOpts := convertSSHOpts(remoteCopy.SSHOpts)
|
||||||
|
sshOpts.SSHErrorsToTty = true
|
||||||
|
if remoteCopy.ConnectMode != sstore.ConnectModeManual && remoteCopy.SSHOpts.SSHPassword == "" && !interactive {
|
||||||
|
sshOpts.BatchMode = true
|
||||||
|
}
|
||||||
|
client, err := sshOpts.ConnectToClient()
|
||||||
|
if err != nil {
|
||||||
|
msh.WriteToPtyBuffer("*error, ssh cannot connect to client: %v\n", err)
|
||||||
|
}
|
||||||
|
makeClientCtx, makeClientCancelFn := context.WithCancel(context.Background())
|
||||||
|
defer makeClientCancelFn()
|
||||||
|
msh.WithLock(func() {
|
||||||
|
msh.Err = nil
|
||||||
|
msh.ErrNoInitPk = false
|
||||||
|
msh.Status = StatusConnecting
|
||||||
|
msh.MakeClientCancelFn = makeClientCancelFn
|
||||||
|
deadlineTime := time.Now().Add(RemoteConnectTimeout)
|
||||||
|
msh.MakeClientDeadline = &deadlineTime
|
||||||
|
go msh.NotifyRemoteUpdate()
|
||||||
|
})
|
||||||
|
go msh.watchClientDeadlineTime()
|
||||||
|
session, err := client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
msh.WriteToPtyBuffer("*error, ssh cannot create session: %v\n", err)
|
||||||
|
}
|
||||||
|
cproc, initPk, err := shexec.MakeClientProc(makeClientCtx, shexec.SessionWrap{Session: session, StartCmd: MakeServerRunOnlyCommandStr()})
|
||||||
|
// TODO check if initPk.State is not nil
|
||||||
|
var mshellVersion string
|
||||||
|
var hitDeadline bool
|
||||||
|
msh.WithLock(func() {
|
||||||
|
msh.MakeClientCancelFn = nil
|
||||||
|
if time.Now().After(*msh.MakeClientDeadline) {
|
||||||
|
hitDeadline = true
|
||||||
|
}
|
||||||
|
msh.MakeClientDeadline = nil
|
||||||
|
if initPk == nil {
|
||||||
|
msh.ErrNoInitPk = true
|
||||||
|
}
|
||||||
|
if initPk != nil {
|
||||||
|
msh.UName = initPk.UName
|
||||||
|
mshellVersion = initPk.Version
|
||||||
|
if semver.Compare(mshellVersion, scbase.MShellVersion) < 0 {
|
||||||
|
// only set NeedsMShellUpgrade if we got an InitPk
|
||||||
|
msh.NeedsMShellUpgrade = true
|
||||||
|
}
|
||||||
|
msh.InitPkShellType = initPk.Shell
|
||||||
|
}
|
||||||
|
msh.StateMap.Clear()
|
||||||
|
// no notify here, because we'll call notify in either case below
|
||||||
|
})
|
||||||
|
if err == context.Canceled {
|
||||||
|
if hitDeadline {
|
||||||
|
msh.WriteToPtyBuffer("*connect timeout\n")
|
||||||
|
msh.setErrorStatus(errors.New("connect timeout"))
|
||||||
|
} else {
|
||||||
|
msh.WriteToPtyBuffer("*forced disconnection\n")
|
||||||
|
msh.WithLock(func() {
|
||||||
|
msh.Status = StatusDisconnected
|
||||||
|
go msh.NotifyRemoteUpdate()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err == nil && semver.MajorMinor(mshellVersion) != semver.MajorMinor(scbase.MShellVersion) {
|
||||||
|
err = fmt.Errorf("mshell version is not compatible current=%s remote=%s", scbase.MShellVersion, mshellVersion)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
cs := fmt.Sprintf("error: %v\n", err)
|
||||||
|
os.WriteFile("/Users/oneirocosm/.waveterm-dev/temp.txt", []byte(cs), 0644)
|
||||||
|
msh.setErrorStatus(err)
|
||||||
|
msh.WriteToPtyBuffer("*error connecting to remote: %v\n", err)
|
||||||
|
go msh.tryAutoInstall()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msh.updateRemoteStateVars(context.Background(), msh.RemoteId, initPk)
|
||||||
|
msh.WithLock(func() {
|
||||||
|
msh.ServerProc = cproc
|
||||||
|
msh.Status = StatusConnected
|
||||||
|
})
|
||||||
|
go func() {
|
||||||
|
exitErr := cproc.Cmd.Wait()
|
||||||
|
exitCode := shexec.GetExitCode(exitErr)
|
||||||
|
msh.WithLock(func() {
|
||||||
|
if msh.Status == StatusConnected || msh.Status == StatusConnecting {
|
||||||
|
msh.Status = StatusDisconnected
|
||||||
|
go msh.NotifyRemoteUpdate()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
msh.WriteToPtyBuffer("*disconnected exitcode=%d\n", exitCode)
|
||||||
|
}()
|
||||||
|
go msh.ProcessPackets()
|
||||||
|
msh.initActiveShells()
|
||||||
|
go msh.NotifyRemoteUpdate()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (msh *MShellProc) Launch(interactive bool) {
|
func (msh *MShellProc) Launch(interactive bool) {
|
||||||
|
remoteCopy := msh.GetRemoteCopy()
|
||||||
|
if remoteCopy.Archived {
|
||||||
|
msh.WriteToPtyBuffer("cannot launch archived remote\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
curStatus := msh.GetStatus()
|
||||||
|
if curStatus == StatusConnected {
|
||||||
|
msh.WriteToPtyBuffer("remote is already connected (no action taken)\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if curStatus == StatusConnecting {
|
||||||
|
msh.WriteToPtyBuffer("remote is already connecting, disconnect before trying to connect again\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sapi, err := shellapi.MakeShellApi(msh.GetShellType())
|
||||||
|
if err != nil {
|
||||||
|
msh.WriteToPtyBuffer("*error, %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
istatus := msh.GetInstallStatus()
|
||||||
|
if istatus == StatusConnecting {
|
||||||
|
msh.WriteToPtyBuffer("remote is trying to install, cancel install before trying to connect again\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if remoteCopy.SSHOpts.SSHPort != 0 && remoteCopy.SSHOpts.SSHPort != 22 {
|
||||||
|
msh.WriteToPtyBuffer("connecting to %s (port %d)...\n", remoteCopy.RemoteCanonicalName, remoteCopy.SSHOpts.SSHPort)
|
||||||
|
} else {
|
||||||
|
msh.WriteToPtyBuffer("connecting to %s...\n", remoteCopy.RemoteCanonicalName)
|
||||||
|
}
|
||||||
|
sshOpts := convertSSHOpts(remoteCopy.SSHOpts)
|
||||||
|
sshOpts.SSHErrorsToTty = true
|
||||||
|
if remoteCopy.ConnectMode != sstore.ConnectModeManual && remoteCopy.SSHOpts.SSHPassword == "" && !interactive {
|
||||||
|
sshOpts.BatchMode = true
|
||||||
|
}
|
||||||
|
makeClientCtx, makeClientCancelFn := context.WithCancel(context.Background())
|
||||||
|
defer makeClientCancelFn()
|
||||||
|
msh.WithLock(func() {
|
||||||
|
msh.Err = nil
|
||||||
|
msh.ErrNoInitPk = false
|
||||||
|
msh.Status = StatusConnecting
|
||||||
|
msh.MakeClientCancelFn = makeClientCancelFn
|
||||||
|
deadlineTime := time.Now().Add(RemoteConnectTimeout)
|
||||||
|
msh.MakeClientDeadline = &deadlineTime
|
||||||
|
go msh.NotifyRemoteUpdate()
|
||||||
|
})
|
||||||
|
go msh.watchClientDeadlineTime()
|
||||||
|
var cmdStr string
|
||||||
|
var cproc *shexec.ClientProc
|
||||||
|
var initPk *packet.InitPacketType
|
||||||
|
if sshOpts.SSHHost == "" && remoteCopy.Local {
|
||||||
|
cmdStr, err = MakeLocalMShellCommandStr(remoteCopy.IsSudo())
|
||||||
|
if err != nil {
|
||||||
|
msh.WriteToPtyBuffer("*error, cannot find local mshell binary: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ecmd := sshOpts.MakeSSHExecCmd(cmdStr, sapi)
|
||||||
|
var cmdPty *os.File
|
||||||
|
cmdPty, err = msh.addControllingTty(ecmd)
|
||||||
|
if err != nil {
|
||||||
|
statusErr := fmt.Errorf("cannot attach controlling tty to mshell command: %w", err)
|
||||||
|
msh.WriteToPtyBuffer("*error, %s\n", statusErr.Error())
|
||||||
|
msh.setErrorStatus(statusErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if len(ecmd.ExtraFiles) > 0 {
|
||||||
|
ecmd.ExtraFiles[len(ecmd.ExtraFiles)-1].Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go msh.RunPtyReadLoop(cmdPty)
|
||||||
|
if remoteCopy.SSHOpts.SSHPassword != "" {
|
||||||
|
go msh.WaitAndSendPassword(remoteCopy.SSHOpts.SSHPassword)
|
||||||
|
}
|
||||||
|
cproc, initPk, err = shexec.MakeClientProc(makeClientCtx, shexec.CmdWrap{Cmd: ecmd})
|
||||||
|
} else {
|
||||||
|
var client *ssh.Client
|
||||||
|
client, err = sshOpts.ConnectToClient()
|
||||||
|
es := fmt.Sprintf("err: %v\n", err)
|
||||||
|
os.WriteFile("/Users/oneirocosm/.waveterm-dev/temp.txt", []byte(es), 0644)
|
||||||
|
if err != nil {
|
||||||
|
msh.WriteToPtyBuffer("*error, ssh cannot connect to client: %v\n", err)
|
||||||
|
}
|
||||||
|
var session *ssh.Session
|
||||||
|
session, err = client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
msh.WriteToPtyBuffer("*error, ssh cannot create session: %v\n", err)
|
||||||
|
}
|
||||||
|
cproc, initPk, err = shexec.MakeClientProc(makeClientCtx, shexec.SessionWrap{Session: session, StartCmd: MakeServerRunOnlyCommandStr()})
|
||||||
|
}
|
||||||
|
// TODO check if initPk.State is not nil
|
||||||
|
var mshellVersion string
|
||||||
|
var hitDeadline bool
|
||||||
|
msh.WithLock(func() {
|
||||||
|
msh.MakeClientCancelFn = nil
|
||||||
|
if time.Now().After(*msh.MakeClientDeadline) {
|
||||||
|
hitDeadline = true
|
||||||
|
}
|
||||||
|
msh.MakeClientDeadline = nil
|
||||||
|
if initPk == nil {
|
||||||
|
msh.ErrNoInitPk = true
|
||||||
|
}
|
||||||
|
if initPk != nil {
|
||||||
|
msh.UName = initPk.UName
|
||||||
|
mshellVersion = initPk.Version
|
||||||
|
if semver.Compare(mshellVersion, scbase.MShellVersion) < 0 {
|
||||||
|
// only set NeedsMShellUpgrade if we got an InitPk
|
||||||
|
msh.NeedsMShellUpgrade = true
|
||||||
|
}
|
||||||
|
msh.InitPkShellType = initPk.Shell
|
||||||
|
}
|
||||||
|
msh.StateMap.Clear()
|
||||||
|
// no notify here, because we'll call notify in either case below
|
||||||
|
})
|
||||||
|
if err == context.Canceled {
|
||||||
|
if hitDeadline {
|
||||||
|
msh.WriteToPtyBuffer("*connect timeout\n")
|
||||||
|
msh.setErrorStatus(errors.New("connect timeout"))
|
||||||
|
} else {
|
||||||
|
msh.WriteToPtyBuffer("*forced disconnection\n")
|
||||||
|
msh.WithLock(func() {
|
||||||
|
msh.Status = StatusDisconnected
|
||||||
|
go msh.NotifyRemoteUpdate()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err == nil && semver.MajorMinor(mshellVersion) != semver.MajorMinor(scbase.MShellVersion) {
|
||||||
|
err = fmt.Errorf("mshell version is not compatible current=%s remote=%s", scbase.MShellVersion, mshellVersion)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
msh.setErrorStatus(err)
|
||||||
|
msh.WriteToPtyBuffer("*error connecting to remote: %v\n", err)
|
||||||
|
go msh.tryAutoInstall()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msh.updateRemoteStateVars(context.Background(), msh.RemoteId, initPk)
|
||||||
|
msh.WithLock(func() {
|
||||||
|
msh.ServerProc = cproc
|
||||||
|
msh.Status = StatusConnected
|
||||||
|
})
|
||||||
|
go func() {
|
||||||
|
exitErr := cproc.Cmd.Wait()
|
||||||
|
exitCode := shexec.GetExitCode(exitErr)
|
||||||
|
msh.WithLock(func() {
|
||||||
|
if msh.Status == StatusConnected || msh.Status == StatusConnecting {
|
||||||
|
msh.Status = StatusDisconnected
|
||||||
|
go msh.NotifyRemoteUpdate()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
msh.WriteToPtyBuffer("*disconnected exitcode=%d\n", exitCode)
|
||||||
|
}()
|
||||||
|
go msh.ProcessPackets()
|
||||||
|
msh.initActiveShells()
|
||||||
|
go msh.NotifyRemoteUpdate()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msh *MShellProc) LaunchOld(interactive bool) {
|
||||||
remoteCopy := msh.GetRemoteCopy()
|
remoteCopy := msh.GetRemoteCopy()
|
||||||
if remoteCopy.Archived {
|
if remoteCopy.Archived {
|
||||||
msh.WriteToPtyBuffer("cannot launch archived remote\n")
|
msh.WriteToPtyBuffer("cannot launch archived remote\n")
|
||||||
@ -1281,7 +1574,7 @@ func (msh *MShellProc) Launch(interactive bool) {
|
|||||||
go msh.NotifyRemoteUpdate()
|
go msh.NotifyRemoteUpdate()
|
||||||
})
|
})
|
||||||
go msh.watchClientDeadlineTime()
|
go msh.watchClientDeadlineTime()
|
||||||
cproc, initPk, err := shexec.MakeClientProc(makeClientCtx, ecmd)
|
cproc, initPk, err := shexec.MakeClientProc(makeClientCtx, shexec.CmdWrap{Cmd: ecmd})
|
||||||
// TODO check if initPk.State is not nil
|
// TODO check if initPk.State is not nil
|
||||||
var mshellVersion string
|
var mshellVersion string
|
||||||
var hitDeadline bool
|
var hitDeadline bool
|
||||||
|
Loading…
Reference in New Issue
Block a user