mirror of
https://github.com/veops/oneterm.git
synced 2025-09-27 03:36:02 +08:00
139 lines
3.3 KiB
Go
139 lines
3.3 KiB
Go
package protocols
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/spf13/cast"
|
|
"go.uber.org/zap"
|
|
gossh "golang.org/x/crypto/ssh"
|
|
|
|
"github.com/veops/oneterm/internal/model"
|
|
"github.com/veops/oneterm/internal/repository"
|
|
gsession "github.com/veops/oneterm/internal/session"
|
|
"github.com/veops/oneterm/internal/tunneling"
|
|
"github.com/veops/oneterm/pkg/logger"
|
|
)
|
|
|
|
// ConnectSsh connects to SSH server
|
|
func ConnectSsh(ctx *gin.Context, sess *gsession.Session, asset *model.Asset, account *model.Account, gateway *model.Gateway) (err error) {
|
|
w, h := cast.ToInt(ctx.Query("w")), cast.ToInt(ctx.Query("h"))
|
|
chs := sess.Chans
|
|
defer func() {
|
|
if err != nil {
|
|
chs.ErrChan <- err
|
|
}
|
|
}()
|
|
|
|
ip, port, err := tunneling.Proxy(false, sess.SessionId, "ssh", asset, gateway)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
auth, err := repository.GetAuth(account)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
sshCli, err := gossh.Dial("tcp", fmt.Sprintf("%s:%d", ip, port), &gossh.ClientConfig{
|
|
User: account.Account,
|
|
Auth: []gossh.AuthMethod{auth},
|
|
HostKeyCallback: gossh.InsecureIgnoreHostKey(),
|
|
Timeout: time.Second,
|
|
})
|
|
if err != nil {
|
|
logger.L().Error("ssh dial failed", zap.Error(err))
|
|
return
|
|
}
|
|
|
|
// CRITICAL: Store SSH client in session for file transfer reuse
|
|
sess.SetSSHClient(sshCli)
|
|
logger.L().Info("SSH client stored in session for reuse", zap.String("sessionId", sess.SessionId))
|
|
|
|
sshSess, err := sshCli.NewSession()
|
|
if err != nil {
|
|
logger.L().Error("ssh session create failed", zap.Error(err))
|
|
return
|
|
}
|
|
defer sshSess.Close()
|
|
|
|
sshSess.Stdin = chs.Rin
|
|
sshSess.Stdout = chs.Wout
|
|
sshSess.Stderr = chs.Wout
|
|
|
|
modes := gossh.TerminalModes{
|
|
gossh.ECHO: 1,
|
|
gossh.TTY_OP_ISPEED: 14400,
|
|
gossh.TTY_OP_OSPEED: 14400,
|
|
}
|
|
if err = sshSess.RequestPty("xterm", h, w, modes); err != nil {
|
|
logger.L().Error("ssh request pty failed", zap.Error(err))
|
|
return
|
|
}
|
|
if err = sshSess.Shell(); err != nil {
|
|
logger.L().Error("ssh start shell failed", zap.Error(err))
|
|
return
|
|
}
|
|
|
|
sess.G.Go(func() error {
|
|
err = sshSess.Wait()
|
|
// Always close AwayChan when SSH session ends
|
|
sess.Once.Do(func() { close(chs.AwayChan) })
|
|
if err != nil {
|
|
return fmt.Errorf("ssh session wait end with error: %w", err)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
chs.ErrChan <- err
|
|
|
|
sess.G.Go(func() error {
|
|
buf := bufio.NewReader(chs.Rout)
|
|
for {
|
|
select {
|
|
case <-sess.Gctx.Done():
|
|
return nil
|
|
default:
|
|
rn, size, err := buf.ReadRune()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if size <= 0 || rn == utf8.RuneError {
|
|
continue
|
|
}
|
|
p := make([]byte, utf8.RuneLen(rn))
|
|
utf8.EncodeRune(p, rn)
|
|
chs.OutChan <- p
|
|
}
|
|
}
|
|
})
|
|
sess.G.Go(func() error {
|
|
defer sshSess.Close()
|
|
defer sess.Chans.Rout.Close()
|
|
defer sess.Chans.Win.Close()
|
|
for {
|
|
select {
|
|
case <-sess.Gctx.Done():
|
|
return nil
|
|
case <-chs.AwayChan:
|
|
// Normal termination - return sentinel error
|
|
return ErrSessionClosed
|
|
case window := <-chs.WindowChan:
|
|
if err := sshSess.WindowChange(window.Height, window.Width); err != nil {
|
|
logger.L().Warn("reset window size failed", zap.Error(err))
|
|
continue
|
|
}
|
|
sess.SshRecoder.Resize(window.Width, window.Height)
|
|
sess.SshParser.Resize(window.Width, window.Height)
|
|
}
|
|
}
|
|
})
|
|
|
|
sess.G.Wait()
|
|
|
|
return
|
|
}
|