309 lines
7.4 KiB
Go
309 lines
7.4 KiB
Go
package guacamole
|
||
|
||
import (
|
||
"bufio"
|
||
"encoding/base64"
|
||
"errors"
|
||
"fmt"
|
||
"net"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/gorilla/websocket"
|
||
)
|
||
|
||
const (
|
||
EnableRecording = "enable-recording"
|
||
RecordingPath = "recording-path"
|
||
CreateRecordingPath = "create-recording-path"
|
||
|
||
FontName = "font-name"
|
||
FontSize = "font-size"
|
||
ColorScheme = "color-scheme"
|
||
Backspace = "backspace"
|
||
TerminalType = "terminal-type"
|
||
|
||
PreConnectionId = "preconnection-id"
|
||
PreConnectionBlob = "preconnection-blob"
|
||
|
||
EnableDrive = "enable-drive"
|
||
DriveName = "drive-name"
|
||
DrivePath = "drive-path"
|
||
EnableWallpaper = "enable-wallpaper"
|
||
EnableTheming = "enable-theming"
|
||
EnableFontSmoothing = "enable-font-smoothing"
|
||
EnableFullWindowDrag = "enable-full-window-drag"
|
||
EnableDesktopComposition = "enable-desktop-composition"
|
||
EnableMenuAnimations = "enable-menu-animations"
|
||
DisableBitmapCaching = "disable-bitmap-caching"
|
||
DisableOffscreenCaching = "disable-offscreen-caching"
|
||
// DisableGlyphCaching Deprecated
|
||
DisableGlyphCaching = "disable-glyph-caching"
|
||
ForceLossless = "force-lossless"
|
||
|
||
Domain = "domain"
|
||
RemoteApp = "remote-app"
|
||
RemoteAppDir = "remote-app-dir"
|
||
RemoteAppArgs = "remote-app-args"
|
||
|
||
ColorDepth = "color-depth"
|
||
Cursor = "cursor"
|
||
SwapRedBlue = "swap-red-blue"
|
||
DestHost = "dest-host"
|
||
DestPort = "dest-port"
|
||
ReadOnly = "read-only"
|
||
|
||
UsernameRegex = "username-regex"
|
||
PasswordRegex = "password-regex"
|
||
LoginSuccessRegex = "login-success-regex"
|
||
LoginFailureRegex = "login-failure-regex"
|
||
|
||
Namespace = "namespace"
|
||
Pod = "pod"
|
||
Container = "container"
|
||
UesSSL = "use-ssl"
|
||
ClientCert = "client-cert"
|
||
ClientKey = "client-key"
|
||
CaCert = "ca-cert"
|
||
IgnoreCert = "ignore-cert"
|
||
)
|
||
|
||
const Delimiter = ';'
|
||
const Version = "VERSION_1_4_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
|
||
}
|
||
|
||
func (opt *Configuration) SetReadOnlyMode() {
|
||
opt.Parameters[ReadOnly] = "true"
|
||
}
|
||
|
||
func (opt *Configuration) SetParameter(name, value string) {
|
||
opt.Parameters[name] = value
|
||
}
|
||
|
||
func (opt *Configuration) UnSetParameter(name string) {
|
||
delete(opt.Parameters, name)
|
||
}
|
||
|
||
func (opt *Configuration) GetParameter(name string) string {
|
||
return opt.Parameters[name]
|
||
}
|
||
|
||
type Instruction struct {
|
||
Opcode string
|
||
Args []string
|
||
ProtocolForm string
|
||
}
|
||
|
||
func NewInstruction(opcode string, args ...string) (ret Instruction) {
|
||
ret.Opcode = opcode
|
||
ret.Args = args
|
||
return ret
|
||
}
|
||
|
||
func (opt *Instruction) String() string {
|
||
if len(opt.ProtocolForm) > 0 {
|
||
return opt.ProtocolForm
|
||
}
|
||
|
||
opt.ProtocolForm = fmt.Sprintf("%d.%s", len(opt.Opcode), opt.Opcode)
|
||
for _, value := range opt.Args {
|
||
opt.ProtocolForm += fmt.Sprintf(",%d.%s", len(value), value)
|
||
}
|
||
opt.ProtocolForm += string(Delimiter)
|
||
return opt.ProtocolForm
|
||
}
|
||
|
||
func (opt *Instruction) Parse(content string) Instruction {
|
||
if strings.LastIndex(content, ";") > 0 {
|
||
content = strings.TrimRight(content, ";")
|
||
}
|
||
messages := strings.Split(content, ",")
|
||
|
||
var args = make([]string, len(messages))
|
||
for i := range messages {
|
||
lm := strings.Split(messages[i], ".")
|
||
args[i] = lm[1]
|
||
}
|
||
return NewInstruction(args[0], args[1:]...)
|
||
}
|
||
|
||
type Tunnel struct {
|
||
conn net.Conn
|
||
reader *bufio.Reader
|
||
writer *bufio.Writer
|
||
UUID string
|
||
Config *Configuration
|
||
IsOpen bool
|
||
}
|
||
|
||
func NewTunnel(address string, config *Configuration) (ret *Tunnel, err error) {
|
||
|
||
conn, err := net.DialTimeout("tcp", address, 5*time.Second)
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
ret = &Tunnel{}
|
||
ret.conn = conn
|
||
ret.reader = bufio.NewReader(conn)
|
||
ret.writer = bufio.NewWriter(conn)
|
||
ret.Config = config
|
||
|
||
selectArg := config.ConnectionID
|
||
if selectArg == "" {
|
||
selectArg = config.Protocol
|
||
}
|
||
|
||
if err := ret.WriteInstructionAndFlush(NewInstruction("select", selectArg)); err != nil {
|
||
_ = conn.Close()
|
||
return nil, err
|
||
}
|
||
|
||
args, err := ret.expect("args")
|
||
if err != nil {
|
||
_ = conn.Close()
|
||
return
|
||
}
|
||
|
||
width := config.GetParameter("width")
|
||
height := config.GetParameter("height")
|
||
dpi := config.GetParameter("dpi")
|
||
|
||
// send size
|
||
if err := ret.WriteInstructionAndFlush(NewInstruction("size", width, height, dpi)); err != nil {
|
||
_ = conn.Close()
|
||
return nil, err
|
||
}
|
||
if err := ret.WriteInstructionAndFlush(NewInstruction("audio", "audio/L8", "audio/L16")); err != nil {
|
||
_ = conn.Close()
|
||
return nil, err
|
||
}
|
||
if err := ret.WriteInstructionAndFlush(NewInstruction("video")); err != nil {
|
||
_ = conn.Close()
|
||
return nil, err
|
||
}
|
||
if err := ret.WriteInstructionAndFlush(NewInstruction("image", "image/jpeg", "image/png", "image/webp")); err != nil {
|
||
_ = conn.Close()
|
||
return nil, err
|
||
}
|
||
if err := ret.WriteInstructionAndFlush(NewInstruction("timezone", "Asia/Shanghai")); err != nil {
|
||
_ = conn.Close()
|
||
return nil, err
|
||
}
|
||
|
||
parameters := make([]string, len(args.Args))
|
||
for i := range args.Args {
|
||
argName := args.Args[i]
|
||
if strings.Contains(argName, "VERSION") {
|
||
parameters[i] = Version
|
||
continue
|
||
}
|
||
parameters[i] = config.GetParameter(argName)
|
||
}
|
||
// send connect
|
||
if err := ret.WriteInstructionAndFlush(NewInstruction("connect", parameters...)); err != nil {
|
||
_ = conn.Close()
|
||
return nil, err
|
||
}
|
||
|
||
ready, err := ret.expect("ready")
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
if len(ready.Args) == 0 {
|
||
_ = conn.Close()
|
||
return nil, errors.New("no connection id received")
|
||
}
|
||
|
||
ret.UUID = ready.Args[0]
|
||
ret.IsOpen = true
|
||
return ret, nil
|
||
}
|
||
|
||
func (opt *Tunnel) WriteInstructionAndFlush(instruction Instruction) error {
|
||
if _, err := opt.WriteAndFlush([]byte(instruction.String())); err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (opt *Tunnel) WriteAndFlush(p []byte) (int, error) {
|
||
//fmt.Printf("-> %v\n", string(p))
|
||
nn, err := opt.writer.Write(p)
|
||
if err != nil {
|
||
return nn, err
|
||
}
|
||
err = opt.writer.Flush()
|
||
if err != nil {
|
||
return nn, err
|
||
}
|
||
return nn, nil
|
||
}
|
||
|
||
func (opt *Tunnel) ReadInstruction() (instruction Instruction, err error) {
|
||
msg, err := opt.Read()
|
||
if err != nil {
|
||
return instruction, err
|
||
}
|
||
return instruction.Parse(string(msg)), err
|
||
}
|
||
|
||
func (opt *Tunnel) Read() (p []byte, err error) {
|
||
data, err := opt.reader.ReadBytes(Delimiter)
|
||
if err != nil {
|
||
return
|
||
}
|
||
s := string(data)
|
||
//fmt.Printf("<- %v \n", s)
|
||
if s == "rate=44100,channels=2;" {
|
||
return make([]byte, 0), nil
|
||
}
|
||
if s == "rate=22050,channels=2;" {
|
||
return make([]byte, 0), nil
|
||
}
|
||
if s == "5.audio,1.1,31.audio/L16;" {
|
||
s += "rate=44100,channels=2;"
|
||
}
|
||
return []byte(s), err
|
||
}
|
||
|
||
func (opt *Tunnel) expect(opcode string) (instruction Instruction, err error) {
|
||
instruction, err = opt.ReadInstruction()
|
||
if err != nil {
|
||
return instruction, err
|
||
}
|
||
|
||
if opcode != instruction.Opcode {
|
||
msg := fmt.Sprintf(`expected "%s" instruction but instead received "%s"`, opcode, instruction.Opcode)
|
||
return instruction, errors.New(msg)
|
||
}
|
||
return instruction, nil
|
||
}
|
||
|
||
func (opt *Tunnel) Close() error {
|
||
opt.IsOpen = false
|
||
return opt.conn.Close()
|
||
}
|
||
|
||
func Disconnect(ws *websocket.Conn, code int, reason string) {
|
||
// guacd 无法处理中文字符,所以进行了base64编码。
|
||
encodeReason := base64.StdEncoding.EncodeToString([]byte(reason))
|
||
err := NewInstruction("error", encodeReason, strconv.Itoa(code))
|
||
_ = ws.WriteMessage(websocket.TextMessage, []byte(err.String()))
|
||
disconnect := NewInstruction("disconnect")
|
||
_ = ws.WriteMessage(websocket.TextMessage, []byte(disconnect.String()))
|
||
}
|