mirror of
https://github.com/veops/oneterm.git
synced 2025-10-10 09:40:07 +08:00
feat: guacd for rdp/vnc
This commit is contained in:
@@ -26,6 +26,7 @@ import (
|
||||
myi18n "github.com/veops/oneterm/pkg/i18n"
|
||||
"github.com/veops/oneterm/pkg/logger"
|
||||
"github.com/veops/oneterm/pkg/server/auth/acl"
|
||||
"github.com/veops/oneterm/pkg/server/guacd"
|
||||
"github.com/veops/oneterm/pkg/server/model"
|
||||
"github.com/veops/oneterm/pkg/server/storage/db/mysql"
|
||||
)
|
||||
@@ -158,7 +159,11 @@ func sendMsg(ws *websocket.Conn, session *model.Session, chs *model.SessionChans
|
||||
// @Router /connect/:asset_id/:account_id/:protocol [post]
|
||||
func (c *Controller) Connect(ctx *gin.Context) {
|
||||
chs := makeChans()
|
||||
protocol := ctx.Param("protocol")
|
||||
sessionId := ""
|
||||
|
||||
switch strings.Split(protocol, ":")[0] {
|
||||
case "ssh":
|
||||
resp := &model.SshResp{}
|
||||
go doSsh(ctx, cast.ToInt(ctx.Query("w")), cast.ToInt(ctx.Query("h")), newSshReq(ctx, model.SESSIONACTION_NEW), chs)
|
||||
if err := <-chs.ErrChan; err != nil {
|
||||
@@ -172,7 +177,29 @@ func (c *Controller) Connect(ctx *gin.Context) {
|
||||
ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrConnectServer, Data: map[string]any{"err": resp.Message}})
|
||||
return
|
||||
}
|
||||
v, ok := onlineSession.Load(resp.SessionId)
|
||||
sessionId = resp.SessionId
|
||||
case "vnc", "rdp":
|
||||
asset, account := &model.Asset{}, &model.Account{}
|
||||
if err := mysql.DB.Model(&asset).Where("id = ?", ctx.Param("asset_id")).First(asset).Error; err != nil {
|
||||
ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrInternal, Data: map[string]any{"err": err}})
|
||||
return
|
||||
}
|
||||
if err := mysql.DB.Model(&account).Where("id = ?", ctx.Param("account_id")).First(asset).Error; err != nil {
|
||||
ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrInternal, Data: map[string]any{"err": err}})
|
||||
return
|
||||
}
|
||||
|
||||
t, err := guacd.NewTunnel(protocol, asset, account, nil)
|
||||
if err != nil {
|
||||
ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrConnectServer, Data: map[string]any{"err": err}})
|
||||
return
|
||||
}
|
||||
t.Handshake()
|
||||
default:
|
||||
logger.L.Error("wrong protocol " + protocol)
|
||||
}
|
||||
|
||||
v, ok := onlineSession.Load(sessionId)
|
||||
if !ok {
|
||||
ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrLoadSession, Data: map[string]any{"err": "cannot find in sync map"}})
|
||||
return
|
||||
|
158
backend/pkg/server/guacd/conn.go
Normal file
158
backend/pkg/server/guacd/conn.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package guacd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/veops/oneterm/pkg/server/model"
|
||||
)
|
||||
|
||||
const Version = "VERSION_1_5_0"
|
||||
|
||||
type Configuration struct {
|
||||
ConnectionId string
|
||||
Protocol string
|
||||
Parameters map[string]string
|
||||
}
|
||||
|
||||
func NewConfiguration() (config *Configuration) {
|
||||
config = &Configuration{}
|
||||
config.Parameters = make(map[string]string)
|
||||
return config
|
||||
}
|
||||
|
||||
type Tunnel struct {
|
||||
conn net.Conn
|
||||
reader *bufio.Reader
|
||||
writer *bufio.Writer
|
||||
Uuid string
|
||||
Config *Configuration
|
||||
}
|
||||
|
||||
func NewTunnel(protocol string, asset *model.Asset, account *model.Account, gateway *model.Gateway) (t *Tunnel, err error) {
|
||||
ss := strings.Split(protocol, ":")
|
||||
protocol, port := ss[0], ss[1]
|
||||
|
||||
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", asset.Ip, port), time.Second*3)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
t = &Tunnel{
|
||||
conn: conn,
|
||||
reader: bufio.NewReader(conn),
|
||||
writer: bufio.NewWriter(conn),
|
||||
Config: &Configuration{
|
||||
Protocol: protocol,
|
||||
},
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Handshake
|
||||
//
|
||||
// https://guacamole.apache.org/doc/gug/guacamole-protocol.html#handshake-phase
|
||||
func (t *Tunnel) Handshake() (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
defer t.conn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// select
|
||||
if err = t.WriteInstruction(NewInstruction("select", lo.Ternary(t.Config.ConnectionId == "", t.Config.Protocol, t.Config.ConnectionId))); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// args
|
||||
args, err := t.assert("args")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
parameters := make([]string, len(args.Args))
|
||||
for i, name := range args.Args {
|
||||
if strings.Contains(name, "VERSION") {
|
||||
parameters[i] = Version
|
||||
continue
|
||||
}
|
||||
parameters[i] = t.Config.Parameters[name]
|
||||
}
|
||||
|
||||
// size audio ...
|
||||
if err = t.WriteInstruction(NewInstruction("size", t.Config.Parameters["width"], t.Config.Parameters["height"], t.Config.Parameters["dpi"])); err != nil {
|
||||
return
|
||||
}
|
||||
if err = t.WriteInstruction(NewInstruction("audio", "audio/L8", "audio/L16")); err != nil {
|
||||
return
|
||||
}
|
||||
if err = t.WriteInstruction(NewInstruction("video")); err != nil {
|
||||
return
|
||||
}
|
||||
if err = t.WriteInstruction(NewInstruction("image", "image/jpeg", "image/png", "image/webp")); err != nil {
|
||||
return
|
||||
}
|
||||
if err = t.WriteInstruction(NewInstruction("timezone", "Asia/Shanghai")); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// connect
|
||||
if err = t.WriteInstruction(NewInstruction("connect", parameters...)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// ready
|
||||
ready, err := t.assert("ready")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(ready.Args) == 0 {
|
||||
err = fmt.Errorf("empty connection id")
|
||||
return
|
||||
}
|
||||
|
||||
t.Uuid = ready.Args[0]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (t *Tunnel) WriteInstruction(instruction *Instruction) (err error) {
|
||||
_, err = t.writer.Write([]byte(instruction.String()))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = t.writer.Flush()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *Tunnel) ReadInstruction() (instruction *Instruction, err error) {
|
||||
data, err := t.reader.ReadBytes(Delimiter)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
instruction = (&Instruction{}).Parse(string(data))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (t *Tunnel) assert(opcode string) (instruction *Instruction, err error) {
|
||||
instruction, err = t.ReadInstruction()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if opcode != instruction.Opcode {
|
||||
err = fmt.Errorf(`expect instruction "%s" but got "%s"`, opcode, instruction.Opcode)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
46
backend/pkg/server/guacd/instruction.go
Normal file
46
backend/pkg/server/guacd/instruction.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package guacd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
const Delimiter = ';'
|
||||
|
||||
type Instruction struct {
|
||||
Opcode string
|
||||
Args []string
|
||||
cache string
|
||||
}
|
||||
|
||||
func NewInstruction(opcode string, args ...string) *Instruction {
|
||||
return &Instruction{
|
||||
Opcode: opcode,
|
||||
Args: args,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Instruction) String() string {
|
||||
if len(i.cache) > 0 {
|
||||
return i.cache
|
||||
}
|
||||
|
||||
i.cache = fmt.Sprintf("%d.%s", len(i.Opcode), i.Opcode)
|
||||
for _, value := range i.Args {
|
||||
i.cache += fmt.Sprintf(",%d.%s", len(value), value)
|
||||
}
|
||||
i.cache += string(Delimiter)
|
||||
return i.cache
|
||||
}
|
||||
|
||||
func (i *Instruction) Parse(content string) *Instruction {
|
||||
if strings.LastIndex(content, ";") > 0 {
|
||||
content = strings.TrimRight(content, ";")
|
||||
}
|
||||
elements := strings.Split(content, ",")
|
||||
|
||||
var args = make([]string, len(elements))
|
||||
for i, e := range elements {
|
||||
args[i] = strings.Split(e, ".")[1]
|
||||
}
|
||||
return NewInstruction(args[0], args[1:]...)
|
||||
}
|
Reference in New Issue
Block a user