mirror of
https://github.com/veops/oneterm.git
synced 2025-10-25 00:10:37 +08:00
feat: guacd
This commit is contained in:
@@ -65,36 +65,15 @@ func (c *Controller) Connecting(ctx *gin.Context) {
|
|||||||
defer ws.Close()
|
defer ws.Close()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err == nil {
|
handleError(ctx, sessionId, err, ws)
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.L.Debug("connecting failed", zap.String("session_id", sessionId), zap.Error(err))
|
|
||||||
ae, ok := err.(*ApiError)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lang := ctx.PostForm("lang")
|
|
||||||
accept := ctx.GetHeader("Accept-Language")
|
|
||||||
localizer := i18n.NewLocalizer(conf.Bundle, lang, accept)
|
|
||||||
ws.WriteMessage(websocket.TextMessage, []byte(ae.Message(localizer)))
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
v, ok := onlineSession.Load(sessionId)
|
session, err := loadOnlineSessionById(sessionId)
|
||||||
if !ok {
|
if err != nil {
|
||||||
err = &ApiError{Code: ErrInvalidSessionId, Data: map[string]any{"sessionId": sessionId}}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
session, ok := v.(*model.Session)
|
|
||||||
if !ok {
|
|
||||||
err = &ApiError{Code: ErrLoadSession, Data: map[string]any{"err": "invalid type"}}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if session.Connected.Load() {
|
|
||||||
err = &ApiError{Code: ErrInvalidSessionId, Data: map[string]any{"sessionId": sessionId}}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
session.Connected.CompareAndSwap(false, true)
|
session.Connected.CompareAndSwap(false, true)
|
||||||
if strings.HasPrefix(session.Protocol, "ssh") {
|
if session.IsSsh() {
|
||||||
err = handleSsh(ctx, ws, session)
|
err = handleSsh(ctx, ws, session)
|
||||||
} else {
|
} else {
|
||||||
err = handleGuacd(ctx, ws, session)
|
err = handleGuacd(ctx, ws, session)
|
||||||
@@ -108,23 +87,24 @@ func handleSsh(ctx *gin.Context, ws *websocket.Conn, session *model.Session) (er
|
|||||||
}()
|
}()
|
||||||
chs.WindowChan <- fmt.Sprintf("%s,%s,%s", ctx.Query("w"), ctx.Query("h"), ctx.Query("dpi"))
|
chs.WindowChan <- fmt.Sprintf("%s,%s,%s", ctx.Query("w"), ctx.Query("h"), ctx.Query("dpi"))
|
||||||
tk, tk1s := time.NewTicker(time.Millisecond*100), time.NewTicker(time.Second)
|
tk, tk1s := time.NewTicker(time.Millisecond*100), time.NewTicker(time.Second)
|
||||||
g := &errgroup.Group{}
|
g, gctx := errgroup.WithContext(ctx)
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
return readWsMsg(ctx, ws, chs)
|
return readWsMsg(gctx, ws, chs)
|
||||||
})
|
})
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-gctx.Done():
|
||||||
return nil
|
return nil
|
||||||
case closeBy := <-chs.CloseChan:
|
case closeBy := <-chs.CloseChan:
|
||||||
out := []byte("\r\n \033[31m closed by admin")
|
out := []byte("\r\n \033[31m closed by admin")
|
||||||
ws.WriteMessage(websocket.TextMessage, out)
|
ws.WriteMessage(websocket.TextMessage, out)
|
||||||
writeToMonitors(session.Monitors, out)
|
writeToMonitors(session.Monitors, out)
|
||||||
logger.L.Warn("close by admin", zap.String("username", closeBy))
|
err := fmt.Errorf("colse by admin %s", closeBy)
|
||||||
return nil
|
logger.L.Warn(err.Error())
|
||||||
|
return err
|
||||||
case err := <-chs.ErrChan:
|
case err := <-chs.ErrChan:
|
||||||
logger.L.Error("disconnected", zap.Error(err))
|
logger.L.Error("server disconnected", zap.Error(err))
|
||||||
return err
|
return err
|
||||||
case in := <-chs.InChan:
|
case in := <-chs.InChan:
|
||||||
rt := in[0]
|
rt := in[0]
|
||||||
@@ -164,14 +144,13 @@ func handleGuacd(ctx *gin.Context, ws *websocket.Conn, session *model.Session) (
|
|||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
|
||||||
return nil
|
|
||||||
case closeBy := <-chs.CloseChan:
|
case closeBy := <-chs.CloseChan:
|
||||||
out := []byte("\r\n \033[31m closed by admin")
|
out := []byte("\r\n \033[31m closed by admin")
|
||||||
ws.WriteMessage(websocket.TextMessage, out)
|
ws.WriteMessage(websocket.TextMessage, out)
|
||||||
writeToMonitors(session.Monitors, out)
|
writeToMonitors(session.Monitors, out)
|
||||||
logger.L.Warn("close by admin", zap.String("username", closeBy))
|
err := fmt.Errorf("colse by admin %s", closeBy)
|
||||||
return nil
|
logger.L.Warn(err.Error())
|
||||||
|
return err
|
||||||
case err := <-chs.ErrChan:
|
case err := <-chs.ErrChan:
|
||||||
logger.L.Error("disconnected", zap.Error(err))
|
logger.L.Error("disconnected", zap.Error(err))
|
||||||
return err
|
return err
|
||||||
@@ -208,16 +187,14 @@ func sendMsg(ws *websocket.Conn, session *model.Session, chs *model.SessionChans
|
|||||||
// @Success 200 {object} HttpResponse{data=model.Session}
|
// @Success 200 {object} HttpResponse{data=model.Session}
|
||||||
// @Router /connect/:asset_id/:account_id/:protocol [post]
|
// @Router /connect/:asset_id/:account_id/:protocol [post]
|
||||||
func (c *Controller) Connect(ctx *gin.Context) {
|
func (c *Controller) Connect(ctx *gin.Context) {
|
||||||
w, h, dpi, protocol, chs := cast.ToInt(ctx.Query("w")), cast.ToInt(ctx.Query("h")), cast.ToInt(ctx.Query("dpi")), ctx.Param("protocol"), makeChans()
|
protocol, chs := ctx.Param("protocol"), makeChans()
|
||||||
sessionId, resp := "", &model.ServerResp{}
|
sessionId, resp := "", &model.ServerResp{}
|
||||||
|
|
||||||
switch strings.Split(protocol, ":")[0] {
|
switch strings.Split(protocol, ":")[0] {
|
||||||
case "ssh":
|
case "ssh":
|
||||||
go doSsh(ctx, w, h, newSshReq(ctx, model.SESSIONACTION_NEW), chs)
|
go doSsh(ctx, newSshReq(ctx, model.SESSIONACTION_NEW), chs)
|
||||||
case "vnc", "rdp":
|
case "vnc", "rdp":
|
||||||
w, h, dpi = cast.ToInt(ctx.Query("screen_width")), cast.ToInt(ctx.Query("screen_height")), cast.ToInt(ctx.Query("screen_dpi"))
|
go doGuacd(ctx, "", protocol, chs)
|
||||||
w, h, dpi = 731, 929, 128
|
|
||||||
go doGuacd(ctx, "", w, h, dpi, protocol, chs)
|
|
||||||
default:
|
default:
|
||||||
logger.L.Error("wrong protocol " + protocol)
|
logger.L.Error("wrong protocol " + protocol)
|
||||||
}
|
}
|
||||||
@@ -253,7 +230,7 @@ func readWsMsg(ctx context.Context, ws *websocket.Conn, chs *model.SessionChans)
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return fmt.Errorf("ctx done")
|
return nil
|
||||||
default:
|
default:
|
||||||
t, msg, err := ws.ReadMessage()
|
t, msg, err := ws.ReadMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -271,8 +248,8 @@ func readWsMsg(ctx context.Context, ws *websocket.Conn, chs *model.SessionChans)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func doSsh(ctx *gin.Context, w, h int, req *model.SshReq, chs *model.SessionChans) {
|
func doSsh(ctx *gin.Context, req *model.SshReq, chs *model.SessionChans) (err error) {
|
||||||
var err error
|
w, h := cast.ToInt(ctx.Query("w")), cast.ToInt(ctx.Query("h"))
|
||||||
defer func() {
|
defer func() {
|
||||||
chs.ErrChan <- err
|
chs.ErrChan <- err
|
||||||
}()
|
}()
|
||||||
@@ -343,34 +320,38 @@ func doSsh(ctx *gin.Context, w, h int, req *model.SshReq, chs *model.SessionChan
|
|||||||
chs.ErrChan <- nil
|
chs.ErrChan <- nil
|
||||||
chs.RespChan <- resp
|
chs.RespChan <- resp
|
||||||
|
|
||||||
waitChan := make(chan error)
|
g, gctx := errgroup.WithContext(context.Background())
|
||||||
g := errgroup.Group{}
|
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
waitChan <- sess.Wait()
|
// TODO
|
||||||
return nil
|
return sess.Wait()
|
||||||
})
|
})
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
for {
|
for {
|
||||||
rn, size, err := buf.ReadRune()
|
select {
|
||||||
if err != nil {
|
case <-gctx.Done():
|
||||||
logger.L.Debug("buf ReadRune failed", zap.Error(err))
|
return nil
|
||||||
return err
|
default:
|
||||||
|
rn, size, err := buf.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
logger.L.Debug("buf ReadRune failed", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if size <= 0 || rn == utf8.RuneError {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p := make([]byte, utf8.RuneLen(rn))
|
||||||
|
utf8.EncodeRune(p, rn)
|
||||||
|
chs.OutChan <- p
|
||||||
}
|
}
|
||||||
if size <= 0 || rn == utf8.RuneError {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
p := make([]byte, utf8.RuneLen(rn))
|
|
||||||
utf8.EncodeRune(p, rn)
|
|
||||||
chs.OutChan <- p
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case err = <-waitChan:
|
case <-gctx.Done():
|
||||||
return err
|
return nil
|
||||||
case <-chs.AwayChan:
|
case <-chs.AwayChan:
|
||||||
return fmt.Errorf("away")
|
return nil
|
||||||
case s := <-chs.WindowChan:
|
case s := <-chs.WindowChan:
|
||||||
wh := strings.Split(s, ",")
|
wh := strings.Split(s, ",")
|
||||||
if len(wh) < 2 {
|
if len(wh) < 2 {
|
||||||
@@ -390,6 +371,8 @@ func doSsh(ctx *gin.Context, w, h int, req *model.SshReq, chs *model.SessionChan
|
|||||||
if err = g.Wait(); err != nil {
|
if err = g.Wait(); err != nil {
|
||||||
logger.L.Warn("doSsh stopped", zap.Error(err))
|
logger.L.Warn("doSsh stopped", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeChans() *model.SessionChans {
|
func makeChans() *model.SessionChans {
|
||||||
@@ -424,7 +407,9 @@ func newSshReq(ctx *gin.Context, action int) *model.SshReq {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func doGuacd(ctx *gin.Context, connection string, w, h, dpi int, protocol string, chs *model.SessionChans) {
|
func doGuacd(ctx *gin.Context, connection string, protocol string, chs *model.SessionChans) {
|
||||||
|
w, h, dpi := cast.ToInt(ctx.Query("w")), cast.ToInt(ctx.Query("h")), cast.ToInt(ctx.Query("dpi"))
|
||||||
|
w, h, dpi = 731, 929, 128 //TODO
|
||||||
currentUser, _ := acl.GetSessionFromCtx(ctx)
|
currentUser, _ := acl.GetSessionFromCtx(ctx)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@@ -458,19 +443,24 @@ func doGuacd(ctx *gin.Context, connection string, w, h, dpi int, protocol string
|
|||||||
logger.L.Error("guacd tunnel failed", zap.Error(err))
|
logger.L.Error("guacd tunnel failed", zap.Error(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = t.Handshake(); err != nil {
|
|
||||||
logger.L.Error("guacd handshake failed", zap.Error(err))
|
session := newGuacdSession(ctx, t.SessionId, asset, account, gateway)
|
||||||
return
|
|
||||||
}
|
|
||||||
session := newGuacdSession(ctx, t.ConnectionId, asset, account, gateway)
|
|
||||||
if err = handleUpsertSession(ctx, session); err != nil {
|
if err = handleUpsertSession(ctx, session); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
|
session.Status = model.SESSIONSTATUS_OFFLINE
|
||||||
|
session.ClosedAt = lo.ToPtr(time.Now())
|
||||||
|
if err = handleUpsertSession(ctx, session); err != nil {
|
||||||
|
logger.L.Error("offline guacd session failed", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
resp := &model.ServerResp{
|
resp := &model.ServerResp{
|
||||||
Code: lo.Ternary(err == nil, 0, -1),
|
Code: lo.Ternary(err == nil, 0, -1),
|
||||||
Message: lo.TernaryF(err == nil, func() string { return "" }, func() string { return err.Error() }),
|
Message: lo.TernaryF(err == nil, func() string { return "" }, func() string { return err.Error() }),
|
||||||
SessionId: t.ConnectionId,
|
SessionId: t.SessionId,
|
||||||
Uid: currentUser.GetUid(),
|
Uid: currentUser.GetUid(),
|
||||||
UserName: currentUser.GetUserName(),
|
UserName: currentUser.GetUserName(),
|
||||||
}
|
}
|
||||||
@@ -478,30 +468,32 @@ func doGuacd(ctx *gin.Context, connection string, w, h, dpi int, protocol string
|
|||||||
chs.ErrChan <- nil
|
chs.ErrChan <- nil
|
||||||
chs.RespChan <- resp
|
chs.RespChan <- resp
|
||||||
|
|
||||||
defer func() {
|
g, gctx := errgroup.WithContext(context.Background())
|
||||||
session.Status = model.SESSIONSTATUS_OFFLINE
|
|
||||||
session.ClosedAt = lo.ToPtr(time.Now())
|
|
||||||
}()
|
|
||||||
|
|
||||||
g := &errgroup.Group{}
|
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
for {
|
for {
|
||||||
p, err := t.Read()
|
select {
|
||||||
if err != nil {
|
case <-gctx.Done():
|
||||||
logger.L.Debug("read instruction failed", zap.Error(err))
|
return nil
|
||||||
return err
|
default:
|
||||||
|
p, err := t.Read()
|
||||||
|
if err != nil {
|
||||||
|
logger.L.Debug("read instruction failed", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(p) <= 0 || bytes.HasPrefix(p, guacd.InternalOpcodeIns) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
chs.OutChan <- p
|
||||||
}
|
}
|
||||||
if len(p) <= 0 || bytes.HasPrefix(p, guacd.InternalOpcodeIns) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
chs.OutChan <- p
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
case <-gctx.Done():
|
||||||
|
return nil
|
||||||
case <-chs.AwayChan:
|
case <-chs.AwayChan:
|
||||||
return fmt.Errorf("away")
|
return nil
|
||||||
case in := <-chs.InChan:
|
case in := <-chs.InChan:
|
||||||
if !bytes.HasPrefix(in, guacd.InternalOpcodeIns) {
|
if !bytes.HasPrefix(in, guacd.InternalOpcodeIns) {
|
||||||
t.Write(in)
|
t.Write(in)
|
||||||
@@ -562,19 +554,7 @@ func (c *Controller) ConnectMonitor(ctx *gin.Context) {
|
|||||||
defer ws.Close()
|
defer ws.Close()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err == nil {
|
handleError(ctx, sessionId, err, ws)
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.L.Debug("monitor failed", zap.String("session_id", sessionId), zap.Error(err))
|
|
||||||
ae, ok := err.(*ApiError)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lang := ctx.PostForm("lang")
|
|
||||||
accept := ctx.GetHeader("Accept-Language")
|
|
||||||
localizer := i18n.NewLocalizer(conf.Bundle, lang, accept)
|
|
||||||
ws.WriteMessage(websocket.TextMessage, []byte(ae.Message(localizer)))
|
|
||||||
ctx.AbortWithError(http.StatusBadRequest, err)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if !acl.IsAdmin(currentUser) {
|
if !acl.IsAdmin(currentUser) {
|
||||||
@@ -582,44 +562,27 @@ func (c *Controller) ConnectMonitor(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
session := &model.Session{}
|
session, err := loadOnlineSessionById(sessionId)
|
||||||
err = mysql.DB.
|
|
||||||
Where("session_id = ?", sessionId).
|
|
||||||
Where("status = ?", model.SESSIONSTATUS_ONLINE).
|
|
||||||
First(session).
|
|
||||||
Error
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
onlineSession.Delete(sessionId)
|
|
||||||
}
|
|
||||||
ctx.AbortWithError(http.StatusBadRequest, &ApiError{Code: ErrInvalidSessionId, Data: map[string]any{"sessionId": sessionId}})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
v, ok := onlineSession.Load(sessionId)
|
g := &errgroup.Group{}
|
||||||
if !ok {
|
|
||||||
ctx.AbortWithError(http.StatusBadRequest, &ApiError{Code: ErrInvalidSessionId, Data: map[string]any{"sessionId": sessionId}})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
session, ok = v.(*model.Session)
|
|
||||||
if !ok {
|
|
||||||
ctx.AbortWithError(http.StatusBadRequest, &ApiError{Code: ErrInvalidSessionId, Data: map[string]any{"sessionId": sessionId}})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch session.SessionType {
|
switch session.SessionType {
|
||||||
case model.SESSIONTYPE_WEB:
|
case model.SESSIONTYPE_WEB:
|
||||||
|
if !session.IsSsh() {
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
case model.SESSIONTYPE_CLIENT:
|
case model.SESSIONTYPE_CLIENT:
|
||||||
cur := false
|
// clinet only has ssh type
|
||||||
session.Monitors.Range(func(key, value any) bool {
|
if !session.HasMonitors() {
|
||||||
cur = true
|
|
||||||
return !cur
|
|
||||||
})
|
|
||||||
if !cur {
|
|
||||||
req := newSshReq(ctx, model.SESSIONACTION_MONITOR)
|
req := newSshReq(ctx, model.SESSIONACTION_MONITOR)
|
||||||
req.SessionId = sessionId
|
req.SessionId = sessionId
|
||||||
chs := makeChans()
|
chs := makeChans()
|
||||||
|
session.Chans = chs
|
||||||
logger.L.Debug("connect to monitor client", zap.String("sessionId", sessionId))
|
logger.L.Debug("connect to monitor client", zap.String("sessionId", sessionId))
|
||||||
go doSsh(ctx, cast.ToInt(ctx.Query("w")), cast.ToInt(ctx.Query("h")), req, chs)
|
go doSsh(ctx, req, chs)
|
||||||
if err = <-chs.ErrChan; err != nil {
|
if err = <-chs.ErrChan; err != nil {
|
||||||
err = &ApiError{Code: ErrConnectServer, Data: map[string]any{"err": err}}
|
err = &ApiError{Code: ErrConnectServer, Data: map[string]any{"err": err}}
|
||||||
return
|
return
|
||||||
@@ -630,33 +593,34 @@ func (c *Controller) ConnectMonitor(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
tk := time.NewTicker(time.Millisecond * 100)
|
tk := time.NewTicker(time.Millisecond * 100)
|
||||||
defer sendMsg(nil, session, chs)
|
g.Go(func() error {
|
||||||
go func() {
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case closeBy := <-chs.CloseChan:
|
case closeBy := <-chs.CloseChan:
|
||||||
writeToMonitors(session.Monitors, []byte("\r\n \033[31m closed by admin"))
|
writeToMonitors(session.Monitors, []byte("\r\n \033[31m closed by admin"))
|
||||||
logger.L.Warn("close by admin", zap.String("username", closeBy))
|
logger.L.Warn("close by admin", zap.String("username", closeBy))
|
||||||
return
|
return nil
|
||||||
case err := <-chs.ErrChan:
|
case err := <-chs.ErrChan:
|
||||||
logger.L.Error("ssh connection failed", zap.Error(err))
|
logger.L.Error("ssh connection failed", zap.Error(err))
|
||||||
return
|
return err
|
||||||
case out := <-chs.OutChan:
|
case out := <-chs.OutChan:
|
||||||
chs.Buf.Write(out)
|
chs.Buf.Write(out)
|
||||||
case <-tk.C:
|
case <-tk.C:
|
||||||
sendMsg(nil, session, chs)
|
sendMsg(nil, session, chs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
session.Monitors.Store(key, ws)
|
session.Monitors.Store(key, ws)
|
||||||
defer func() {
|
defer func() {
|
||||||
session.Monitors.Delete(key)
|
session.Monitors.Delete(key)
|
||||||
|
if session.IsSsh() && !session.HasMonitors() {
|
||||||
|
close(session.Chans.AwayChan)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
_, _, err = ws.ReadMessage()
|
_, _, err = ws.ReadMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -695,11 +659,11 @@ func (c *Controller) ConnectClose(ctx *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.L.Info("closing...", zap.String("sessionId", session.SessionId), zap.Int("type", session.SessionType))
|
logger.L.Info("closing...", zap.String("sessionId", session.SessionId), zap.Int("type", session.SessionType))
|
||||||
defer doOfflineOnlineSession(ctx, session.SessionId, currentUser.GetUserName())
|
defer offlineSession(ctx, session.SessionId, currentUser.GetUserName())
|
||||||
chs := makeChans()
|
chs := makeChans()
|
||||||
req := newSshReq(ctx, model.SESSIONACTION_CLOSE)
|
req := newSshReq(ctx, model.SESSIONACTION_CLOSE)
|
||||||
req.SessionId = session.SessionId
|
req.SessionId = session.SessionId
|
||||||
go doSsh(ctx, cast.ToInt(ctx.Query("w")), cast.ToInt(ctx.Query("h")), req, chs)
|
go doSsh(ctx, req, chs)
|
||||||
if err = <-chs.ErrChan; err != nil {
|
if err = <-chs.ErrChan; err != nil {
|
||||||
ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrConnectServer, Data: map[string]any{"err": err}})
|
ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrConnectServer, Data: map[string]any{"err": err}})
|
||||||
return
|
return
|
||||||
@@ -713,7 +677,7 @@ func (c *Controller) ConnectClose(ctx *gin.Context) {
|
|||||||
ctx.JSON(http.StatusOK, defaultHttpResponse)
|
ctx.JSON(http.StatusOK, defaultHttpResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func doOfflineOnlineSession(ctx *gin.Context, sessionId string, closer string) {
|
func offlineSession(ctx *gin.Context, sessionId string, closer string) {
|
||||||
logger.L.Debug("offline", zap.String("session_id", sessionId), zap.String("closer", closer))
|
logger.L.Debug("offline", zap.String("session_id", sessionId), zap.String("closer", closer))
|
||||||
defer onlineSession.Delete(sessionId)
|
defer onlineSession.Delete(sessionId)
|
||||||
v, ok := onlineSession.Load(sessionId)
|
v, ok := onlineSession.Load(sessionId)
|
||||||
@@ -795,7 +759,6 @@ func (c *Controller) TestConnect(ctx *gin.Context) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
ctx.Params = append(ctx.Params, gin.Param{Key: "asset_id", Value: "1"}, gin.Param{Key: "account_id", Value: "1"}, gin.Param{Key: "protocol", Value: "rdp:13389"})
|
ctx.Params = append(ctx.Params, gin.Param{Key: "asset_id", Value: "1"}, gin.Param{Key: "account_id", Value: "1"}, gin.Param{Key: "protocol", Value: "rdp:13389"})
|
||||||
fmt.Println("----------ing", ctx.Query("screen_width"), ctx.Query("screen_height"), ctx.Query("screen_dpi"))
|
|
||||||
c.Connect(ctx)
|
c.Connect(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -821,6 +784,40 @@ func (c *Controller) TestConnecting(ctx *gin.Context) {
|
|||||||
NickName: "",
|
NickName: "",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
fmt.Println("----------", ctx.Query("screen_width"), ctx.Query("screen_height"), ctx.Query("screen_dpi"))
|
|
||||||
c.Connecting(ctx)
|
c.Connecting(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadOnlineSessionById(sessionId string) (session *model.Session, err error) {
|
||||||
|
v, ok := onlineSession.Load(sessionId)
|
||||||
|
if !ok {
|
||||||
|
err = &ApiError{Code: ErrInvalidSessionId, Data: map[string]any{"sessionId": sessionId}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
session, ok = v.(*model.Session)
|
||||||
|
if !ok {
|
||||||
|
err = &ApiError{Code: ErrLoadSession, Data: map[string]any{"err": "invalid type"}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if session.Connected.Load() {
|
||||||
|
err = &ApiError{Code: ErrInvalidSessionId, Data: map[string]any{"sessionId": sessionId}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleError(ctx *gin.Context, sessionId string, err error, ws *websocket.Conn) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.L.Debug("monitor failed", zap.String("session_id", sessionId), zap.Error(err))
|
||||||
|
ae, ok := err.(*ApiError)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lang := ctx.PostForm("lang")
|
||||||
|
accept := ctx.GetHeader("Accept-Language")
|
||||||
|
localizer := i18n.NewLocalizer(conf.Bundle, lang, accept)
|
||||||
|
ws.WriteMessage(websocket.TextMessage, []byte(ae.Message(localizer)))
|
||||||
|
ctx.AbortWithError(http.StatusBadRequest, err)
|
||||||
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ func Init() (err error) {
|
|||||||
ctx := &gin.Context{}
|
ctx := &gin.Context{}
|
||||||
for _, s := range sessions {
|
for _, s := range sessions {
|
||||||
if s.SessionType == model.SESSIONTYPE_WEB {
|
if s.SessionType == model.SESSIONTYPE_WEB {
|
||||||
doOfflineOnlineSession(ctx, s.SessionId, "")
|
offlineSession(ctx, s.SessionId, "")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
s.Monitors = &sync.Map{}
|
s.Monitors = &sync.Map{}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
"github.com/veops/oneterm/pkg/conf"
|
"github.com/veops/oneterm/pkg/conf"
|
||||||
@@ -14,7 +15,11 @@ import (
|
|||||||
"github.com/veops/oneterm/pkg/util"
|
"github.com/veops/oneterm/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Version = "VERSION_1_5_0"
|
const (
|
||||||
|
recordingPath = "/playback"
|
||||||
|
createRecording = "true"
|
||||||
|
ignoreCert = "true"
|
||||||
|
)
|
||||||
|
|
||||||
type Configuration struct {
|
type Configuration struct {
|
||||||
Protocol string
|
Protocol string
|
||||||
@@ -28,10 +33,11 @@ func NewConfiguration() (config *Configuration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Tunnel struct {
|
type Tunnel struct {
|
||||||
|
SessionId string
|
||||||
|
ConnectionId string
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
reader *bufio.Reader
|
reader *bufio.Reader
|
||||||
writer *bufio.Writer
|
writer *bufio.Writer
|
||||||
ConnectionId string
|
|
||||||
Config *Configuration
|
Config *Configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,27 +56,33 @@ func NewTunnel(connectionId string, w, h, dpi int, protocol string, asset *model
|
|||||||
Config: &Configuration{
|
Config: &Configuration{
|
||||||
Protocol: protocol,
|
Protocol: protocol,
|
||||||
Parameters: map[string]string{
|
Parameters: map[string]string{
|
||||||
"width": cast.ToString(w),
|
"recording-path": recordingPath,
|
||||||
"height": cast.ToString(h),
|
"create-recording-path": createRecording,
|
||||||
"dpi": cast.ToString(dpi),
|
"ignore-cert": ignoreCert,
|
||||||
"scheme": protocol,
|
"width": cast.ToString(w),
|
||||||
"hostname": asset.Ip,
|
"height": cast.ToString(h),
|
||||||
"port": port,
|
"dpi": cast.ToString(dpi),
|
||||||
"ignore-cert": "true",
|
"scheme": protocol,
|
||||||
"security": "",
|
"hostname": asset.Ip,
|
||||||
"username": account.Account,
|
"port": port,
|
||||||
"password": util.DecryptAES(account.Password),
|
"username": account.Account,
|
||||||
|
"password": util.DecryptAES(account.Password),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
if t.ConnectionId == "" {
|
||||||
|
t.SessionId = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = t.handshake()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handshake
|
// handshake
|
||||||
//
|
//
|
||||||
// https://guacamole.apache.org/doc/gug/guacamole-protocol.html#handshake-phase
|
// https://guacamole.apache.org/doc/gug/guacamole-protocol.html#handshake-phase
|
||||||
func (t *Tunnel) Handshake() (err error) {
|
func (t *Tunnel) handshake() (err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.conn.Close()
|
t.conn.Close()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package model
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -56,6 +57,20 @@ func (m *Session) TableName() string {
|
|||||||
return "session"
|
return "session"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Session) IsSsh() bool {
|
||||||
|
return strings.HasPrefix(m.Protocol, "ssh")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Session) HasMonitors() (has bool) {
|
||||||
|
m.Monitors.Range(func(key, value any) bool {
|
||||||
|
has = true
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
type SessionCmd struct {
|
type SessionCmd struct {
|
||||||
Id int `json:"id" gorm:"column:id;primarykey"`
|
Id int `json:"id" gorm:"column:id;primarykey"`
|
||||||
SessionId string `json:"session_id" gorm:"column:session_id"`
|
SessionId string `json:"session_id" gorm:"column:session_id"`
|
||||||
|
|||||||
Reference in New Issue
Block a user