From bfafb9e4905f5ba3bae7f0740ea8c47324ea3fdb Mon Sep 17 00:00:00 2001
From: Sylvie Crowe <107814465+oneirocosm@users.noreply.github.com>
Date: Thu, 14 Mar 2024 16:50:58 -0700
Subject: [PATCH] Ssh Extra Fixes (#459)
---
src/app/common/modals/userinput.tsx | 19 ++-
src/types/custom.d.ts | 1 +
wavesrv/pkg/cmdrunner/cmdrunner.go | 29 +++--
wavesrv/pkg/remote/remote.go | 187 +++-------------------------
wavesrv/pkg/remote/sshclient.go | 1 +
wavesrv/pkg/userinput/userinput.go | 1 +
6 files changed, 54 insertions(+), 184 deletions(-)
diff --git a/src/app/common/modals/userinput.tsx b/src/app/common/modals/userinput.tsx
index 19e0bbee0..035163dd0 100644
--- a/src/app/common/modals/userinput.tsx
+++ b/src/app/common/modals/userinput.tsx
@@ -1,7 +1,7 @@
import * as React from "react";
import { GlobalModel } from "@/models";
import { Choose, When, If } from "tsx-control-statements/components";
-import { Modal, PasswordField, Markdown, Checkbox } from "@/elements";
+import { Modal, PasswordField, TextField, Markdown, Checkbox } from "@/elements";
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil";
import "./userinput.less";
@@ -82,8 +82,17 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
{userInputRequest.querytext}
-
-
+
+
+ handleTextKeyDown(e)}
+ />
+
+
{
autoFocus={true}
onKeyDown={(e) => handleTextKeyDown(e)}
/>
-
-
+
+
,/\"'\\[\\]{}=+$@!*-]*$")
var rendererRe = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9_.:-]*$")
@@ -1796,6 +1796,7 @@ func parseRemoteEditArgs(isNew bool, pk *scpacket.FeCommandPacketType, isLocal b
return nil, fmt.Errorf("invalid format of user@host argument")
}
sudoStr, remoteUser, remoteHost, remotePortStr := m[1], m[2], m[3], m[4]
+ remoteUser = strings.Trim(remoteUser, "@")
var uhPort int
if remotePortStr != "" {
var err error
@@ -1837,7 +1838,11 @@ func parseRemoteEditArgs(isNew bool, pk *scpacket.FeCommandPacketType, isLocal b
return nil, fmt.Errorf("invalid port argument, \"%d\" is not in the range of 1 to 65535", portVal)
}
sshOpts.SSHPort = portVal
- canonicalName = remoteUser + "@" + remoteHost
+ if remoteUser == "" {
+ canonicalName = remoteHost
+ } else {
+ canonicalName = remoteUser + "@" + remoteHost
+ }
if portVal != 0 && portVal != 22 {
canonicalName = canonicalName + ":" + strconv.Itoa(portVal)
}
@@ -2136,17 +2141,17 @@ func createSshImportSummary(changeList map[string][]string) string {
func NewHostInfo(hostName string) (*HostInfoType, error) {
userName, _ := ssh_config.GetStrict(hostName, "User")
- if userName == "" {
- // we cannot store a remote with a missing user
- // in the current setup
- return nil, fmt.Errorf("could not parse \"%s\" - no User in config\n", hostName)
+ var canonicalName string
+ if userName != "" {
+ canonicalName = userName + "@" + hostName
+ } else {
+ canonicalName = hostName
}
- canonicalName := userName + "@" + hostName
- // check if user and host are okay
+ // check if canonicalname is okay
m := userHostRe.FindStringSubmatch(canonicalName)
- if m == nil || m[2] == "" || m[3] == "" {
- return nil, fmt.Errorf("could not parse \"%s\" - %s did not fit user@host requirement\n", hostName, canonicalName)
+ if m == nil {
+ return nil, fmt.Errorf("could not parse \"%s\" - %s did not fit user@host requirement", hostName, canonicalName)
}
portStr, _ := ssh_config.GetStrict(hostName, "Port")
@@ -2157,10 +2162,10 @@ func NewHostInfo(hostName string) (*HostInfoType, error) {
portVal, err = strconv.Atoi(portStr)
if err != nil {
// do not make assumptions about port if incorrectly configured
- return nil, fmt.Errorf("could not parse \"%s\" (%s) - %s could not be converted to a valid port\n", hostName, canonicalName, portStr)
+ return nil, fmt.Errorf("could not parse \"%s\" (%s) - %s could not be converted to a valid port", hostName, canonicalName, portStr)
}
if portVal <= 0 || portVal > 65535 {
- return nil, fmt.Errorf("could not parse port \"%d\": number is not valid for a port\n", portVal)
+ return nil, fmt.Errorf("could not parse port \"%d\": number is not valid for a port", portVal)
}
}
identityFile, _ := ssh_config.GetStrict(hostName, "IdentityFile")
diff --git a/wavesrv/pkg/remote/remote.go b/wavesrv/pkg/remote/remote.go
index 3304f3412..c78cf2976 100644
--- a/wavesrv/pkg/remote/remote.go
+++ b/wavesrv/pkg/remote/remote.go
@@ -15,6 +15,7 @@ import (
"os/exec"
"path"
"regexp"
+ "runtime/debug"
"strconv"
"strings"
"sync"
@@ -44,8 +45,6 @@ import (
"golang.org/x/mod/semver"
)
-const UseSshLibrary = true
-
const RemoteTypeMShell = "mshell"
const DefaultTerm = "xterm-256color"
const DefaultMaxPtySize = 1024 * 1024
@@ -129,12 +128,6 @@ type pendingStateKey struct {
RemotePtr sstore.RemotePtrType
}
-// for conditional launch method based on ssh library in use
-// remove once ssh library is stabilized
-type Launcher interface {
- Launch(*MShellProc, bool)
-}
-
type MShellProc struct {
Lock *sync.Mutex
Remote *sstore.RemoteType
@@ -163,7 +156,6 @@ type MShellProc struct {
RunningCmds map[base.CommandKey]*RunCmdType
PendingStateCmds map[pendingStateKey]base.CommandKey // key=[remoteinstance name]
- launcher Launcher // for conditional launch method based on ssh library in use. remove once ssh library is stabilized
Client *ssh.Client
}
@@ -188,12 +180,6 @@ func CanComplete(remoteType string) bool {
}
}
-// for conditional launch method based on ssh library in use
-// remove once ssh library is stabilized
-func (msh *MShellProc) Launch(interactive bool) {
- msh.launcher.Launch(msh, interactive)
-}
-
func (msh *MShellProc) GetStatus() string {
msh.Lock.Lock()
defer msh.Lock.Unlock()
@@ -711,14 +697,8 @@ func MakeMShell(r *sstore.RemoteType) *MShellProc {
RunningCmds: make(map[base.CommandKey]*RunCmdType),
PendingStateCmds: make(map[pendingStateKey]base.CommandKey),
StateMap: server.MakeShellStateMap(),
- launcher: LegacyLauncher{}, // for conditional launch method based on ssh library in use. remove once ssh library is stabilized
DataPosMap: utilfn.MakeSyncMap[base.CommandKey, int64](),
}
- // for conditional launch method based on ssh library in use
- // remove once ssh library is stabilized
- if UseSshLibrary {
- rtn.launcher = NewLauncher{}
- }
rtn.WriteToPtyBuffer("console for connection [%s]\n", r.GetName())
return rtn
@@ -1201,6 +1181,15 @@ func (msh *MShellProc) WaitAndSendPassword(pw string) {
}
func (msh *MShellProc) RunInstall(autoInstall bool) {
+ defer func() {
+ if r := recover(); r != nil {
+ errMsg := fmt.Errorf("this should not happen. if it does, please reach out to us in our discord or open an issue on our github\n\n"+
+ "error:\n%v\n\nstack trace:\n%s", r, string(debug.Stack()))
+ log.Printf("fatal error, %s\n", errMsg)
+ msh.WriteToPtyBuffer("*fatal error, %s\n", errMsg)
+ msh.setErrorStatus(errMsg)
+ }
+ }()
remoteCopy := msh.GetRemoteCopy()
if remoteCopy.Archived {
msh.WriteToPtyBuffer("*error: cannot install on archived remote\n")
@@ -1574,12 +1563,16 @@ func (msh *MShellProc) createWaveshellSession(clientCtx context.Context, remoteC
return wsSession, nil
}
-// for conditional launch method based on ssh library in use
-// remove once ssh library is stabilized
-type NewLauncher struct{}
-
-// func (msh *MShellProc) LaunchNew(interactive bool) {
-func (NewLauncher) Launch(msh *MShellProc, interactive bool) {
+func (msh *MShellProc) Launch(interactive bool) {
+ defer func() {
+ if r := recover(); r != nil {
+ errMsg := fmt.Errorf("this should not happen. if it does, please reach out to us in our discord or open an issue on our github\n\n"+
+ "error:\n%v\n\nstack trace:\n%s", r, string(debug.Stack()))
+ log.Printf("fatal error, %s\n", errMsg)
+ msh.WriteToPtyBuffer("*fatal error, %s\n", errMsg)
+ msh.setErrorStatus(errMsg)
+ }
+ }()
remoteCopy := msh.GetRemoteCopy()
if remoteCopy.Archived {
msh.WriteToPtyBuffer("cannot launch archived remote\n")
@@ -1687,146 +1680,6 @@ func (NewLauncher) Launch(msh *MShellProc, interactive bool) {
go msh.NotifyRemoteUpdate()
}
-// for conditional launch method based on ssh library in use
-// remove once ssh library is stabilized
-type LegacyLauncher struct{}
-
-// func (msh *MShellProc) LaunchLegacy(interactive bool) {
-func (LegacyLauncher) Launch(msh *MShellProc, 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
- }
- var cmdStr string
- if sshOpts.SSHHost == "" && remoteCopy.Local {
- var err error
- cmdStr, err = MakeLocalMShellCommandStr(remoteCopy.IsSudo())
- if err != nil {
- msh.WriteToPtyBuffer("*error, cannot find local mshell binary: %v\n", err)
- return
- }
- } else {
- cmdStr = MakeServerCommandStr()
- }
- ecmd := sshOpts.MakeSSHExecCmd(cmdStr, sapi)
- 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)
- }
- var makeClientCtx context.Context
- var makeClientCancelFn context.CancelFunc
- msh.WithLock(func() {
- deadlineTime := time.Now().Add(RemoteConnectTimeout)
- makeClientCtx, makeClientCancelFn = context.WithDeadline(context.Background(), deadlineTime)
- defer makeClientCancelFn()
- msh.Err = nil
- msh.ErrNoInitPk = false
- msh.Status = StatusConnecting
- msh.MakeClientCancelFn = makeClientCancelFn
- msh.MakeClientDeadline = &deadlineTime
- go msh.NotifyRemoteUpdate()
- })
- go msh.watchClientDeadlineTime()
- cproc, err := shexec.MakeClientProc(makeClientCtx, shexec.CmdWrap{Cmd: ecmd})
- msh.WithLock(func() {
- msh.MakeClientCancelFn = nil
- msh.MakeClientDeadline = nil
- msh.StateMap.Clear()
- // no notify here, because we'll call notify in either case below
- })
- if err == context.DeadlineExceeded {
- msh.WriteToPtyBuffer("*connect timeout\n")
- msh.setErrorStatus(errors.New("connect timeout"))
- return
- } else if err == context.Canceled {
- msh.WriteToPtyBuffer("*forced disconnection\n")
- msh.WithLock(func() {
- msh.Status = StatusDisconnected
- go msh.NotifyRemoteUpdate()
- })
- return
- } else if serr, ok := err.(shexec.WaveshellLaunchError); ok {
- msh.WithLock(func() {
- msh.UName = serr.InitPk.UName
- if semver.Compare(serr.InitPk.Version, scbase.MShellVersion) < 0 {
- // only set NeedsMShellUpgrade if we got an InitPk
- msh.NeedsMShellUpgrade = true
- }
- msh.InitPkShellType = serr.InitPk.Shell
- })
- msh.WriteToPtyBuffer("*error, %s\n", serr.Error())
- msh.setErrorStatus(serr)
- go msh.tryAutoInstall()
- return
- } else if err != nil {
- msh.WriteToPtyBuffer("*error, %s\n", serr.Error())
- msh.setErrorStatus(err)
- return
- }
-
- msh.updateRemoteStateVars(context.Background(), msh.RemoteId, cproc.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()
-}
-
func (msh *MShellProc) initActiveShells() {
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelFn()
diff --git a/wavesrv/pkg/remote/sshclient.go b/wavesrv/pkg/remote/sshclient.go
index 7eff78ef3..b8f008208 100644
--- a/wavesrv/pkg/remote/sshclient.go
+++ b/wavesrv/pkg/remote/sshclient.go
@@ -230,6 +230,7 @@ func promptChallengeQuestion(connCtx context.Context, question string, echo bool
QueryText: queryText,
Markdown: true,
Title: "Keyboard Interactive Authentication",
+ PublicText: echo,
}
response, err := userinput.GetUserInput(ctx, scbus.MainRpcBus, request)
if err != nil {
diff --git a/wavesrv/pkg/userinput/userinput.go b/wavesrv/pkg/userinput/userinput.go
index 50212acb6..d930dd8d4 100644
--- a/wavesrv/pkg/userinput/userinput.go
+++ b/wavesrv/pkg/userinput/userinput.go
@@ -22,6 +22,7 @@ type UserInputRequestType struct {
Markdown bool `json:"markdown"`
TimeoutMs int `json:"timeoutms"`
CheckBoxMsg string `json:"checkboxmsg"`
+ PublicText bool `json:"publictext"`
}
func (*UserInputRequestType) GetType() string {