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 {