mirror of
https://github.com/amitbet/vncproxy.git
synced 2025-09-23 10:28:54 +00:00
Added server for web sockets, plus some refactoring
This commit is contained in:
16
client/README.md
Normal file
16
client/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# VNC Library for Go
|
||||
|
||||
go-vnc is a VNC library for Go, initially supporting VNC clients but
|
||||
with the goal of eventually implementing a VNC server.
|
||||
|
||||
This library implements [RFC 6143](http://tools.ietf.org/html/rfc6143).
|
||||
|
||||
## Usage & Installation
|
||||
|
||||
The library is installable via standard `go get`. The package name is `vnc`.
|
||||
|
||||
```
|
||||
$ go get github.com/mitchellh/go-vnc
|
||||
```
|
||||
|
||||
Documentation is available on GoDoc: http://godoc.org/github.com/mitchellh/go-vnc
|
511
client/client-conn.go
Normal file
511
client/client-conn.go
Normal file
@@ -0,0 +1,511 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"unicode"
|
||||
"vncproxy/common"
|
||||
"vncproxy/tee-listeners"
|
||||
)
|
||||
|
||||
// A ServerMessage implements a message sent from the server to the client.
|
||||
|
||||
// A ClientAuth implements a method of authenticating with a remote server.
|
||||
type ClientAuth interface {
|
||||
// SecurityType returns the byte identifier sent by the server to
|
||||
// identify this authentication scheme.
|
||||
SecurityType() uint8
|
||||
|
||||
// Handshake is called when the authentication handshake should be
|
||||
// performed, as part of the general RFB handshake. (see 7.2.1)
|
||||
Handshake(net.Conn) error
|
||||
}
|
||||
|
||||
type ClientConn struct {
|
||||
conn net.Conn
|
||||
|
||||
//c net.Conn
|
||||
config *ClientConfig
|
||||
|
||||
// If the pixel format uses a color map, then this is the color
|
||||
// map that is used. This should not be modified directly, since
|
||||
// the data comes from the server.
|
||||
ColorMap common.ColorMap
|
||||
|
||||
// Encodings supported by the client. This should not be modified
|
||||
// directly. Instead, SetEncodings should be used.
|
||||
Encs []common.Encoding
|
||||
|
||||
// Width of the frame buffer in pixels, sent from the server.
|
||||
FrameBufferWidth uint16
|
||||
|
||||
// Height of the frame buffer in pixels, sent from the server.
|
||||
FrameBufferHeight uint16
|
||||
|
||||
// Name associated with the desktop, sent from the server.
|
||||
DesktopName string
|
||||
|
||||
// The pixel format associated with the connection. This shouldn't
|
||||
// be modified. If you wish to set a new pixel format, use the
|
||||
// SetPixelFormat method.
|
||||
PixelFormat common.PixelFormat
|
||||
}
|
||||
|
||||
// A ClientConfig structure is used to configure a ClientConn. After
|
||||
// one has been passed to initialize a connection, it must not be modified.
|
||||
type ClientConfig struct {
|
||||
// A slice of ClientAuth methods. Only the first instance that is
|
||||
// suitable by the server will be used to authenticate.
|
||||
Auth []ClientAuth
|
||||
|
||||
// Exclusive determines whether the connection is shared with other
|
||||
// clients. If true, then all other clients connected will be
|
||||
// disconnected when a connection is established to the VNC server.
|
||||
Exclusive bool
|
||||
|
||||
// The channel that all messages received from the server will be
|
||||
// sent on. If the channel blocks, then the goroutine reading data
|
||||
// from the VNC server may block indefinitely. It is up to the user
|
||||
// of the library to ensure that this channel is properly read.
|
||||
// If this is not set, then all messages will be discarded.
|
||||
ServerMessageCh chan<- common.ServerMessage
|
||||
|
||||
// A slice of supported messages that can be read from the server.
|
||||
// This only needs to contain NEW server messages, and doesn't
|
||||
// need to explicitly contain the RFC-required messages.
|
||||
ServerMessages []common.ServerMessage
|
||||
}
|
||||
|
||||
func Client(c net.Conn, cfg *ClientConfig) (*ClientConn, error) {
|
||||
conn := &ClientConn{
|
||||
conn: c,
|
||||
config: cfg,
|
||||
}
|
||||
|
||||
if err := conn.handshake(); err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go conn.mainLoop()
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *ClientConn) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *ClientConn) Encodings() []common.Encoding {
|
||||
return c.Encs
|
||||
}
|
||||
|
||||
func (c *ClientConn) CurrentPixelFormat() *common.PixelFormat {
|
||||
return &c.PixelFormat
|
||||
}
|
||||
|
||||
func (c *ClientConn) CurrentColorMap() *common.ColorMap {
|
||||
return &c.ColorMap
|
||||
}
|
||||
|
||||
// CutText tells the server that the client has new text in its cut buffer.
|
||||
// The text string MUST only contain Latin-1 characters. This encoding
|
||||
// is compatible with Go's native string format, but can only use up to
|
||||
// unicode.MaxLatin values.
|
||||
//
|
||||
// See RFC 6143 Section 7.5.6
|
||||
func (c *ClientConn) CutText(text string) error {
|
||||
var buf bytes.Buffer
|
||||
|
||||
// This is the fixed size data we'll send
|
||||
fixedData := []interface{}{
|
||||
uint8(6),
|
||||
uint8(0),
|
||||
uint8(0),
|
||||
uint8(0),
|
||||
uint32(len(text)),
|
||||
}
|
||||
|
||||
for _, val := range fixedData {
|
||||
if err := binary.Write(&buf, binary.BigEndian, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, char := range text {
|
||||
if char > unicode.MaxLatin1 {
|
||||
return fmt.Errorf("Character '%s' is not valid Latin-1", char)
|
||||
}
|
||||
|
||||
if err := binary.Write(&buf, binary.BigEndian, uint8(char)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
dataLength := 8 + len(text)
|
||||
if _, err := c.conn.Write(buf.Bytes()[0:dataLength]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Requests a framebuffer update from the server. There may be an indefinite
|
||||
// time between the request and the actual framebuffer update being
|
||||
// received.
|
||||
//
|
||||
// See RFC 6143 Section 7.5.3
|
||||
func (c *ClientConn) FramebufferUpdateRequest(incremental bool, x, y, width, height uint16) error {
|
||||
var buf bytes.Buffer
|
||||
var incrementalByte uint8 = 0
|
||||
|
||||
if incremental {
|
||||
incrementalByte = 1
|
||||
}
|
||||
|
||||
data := []interface{}{
|
||||
uint8(3),
|
||||
incrementalByte,
|
||||
x, y, width, height,
|
||||
}
|
||||
|
||||
for _, val := range data {
|
||||
if err := binary.Write(&buf, binary.BigEndian, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := c.conn.Write(buf.Bytes()[0:10]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// KeyEvent indiciates a key press or release and sends it to the server.
|
||||
// The key is indicated using the X Window System "keysym" value. Use
|
||||
// Google to find a reference of these values. To simulate a key press,
|
||||
// you must send a key with both a down event, and a non-down event.
|
||||
//
|
||||
// See 7.5.4.
|
||||
func (c *ClientConn) KeyEvent(keysym uint32, down bool) error {
|
||||
var downFlag uint8 = 0
|
||||
if down {
|
||||
downFlag = 1
|
||||
}
|
||||
|
||||
data := []interface{}{
|
||||
uint8(4),
|
||||
downFlag,
|
||||
uint8(0),
|
||||
uint8(0),
|
||||
keysym,
|
||||
}
|
||||
|
||||
for _, val := range data {
|
||||
if err := binary.Write(c.conn, binary.BigEndian, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PointerEvent indicates that pointer movement or a pointer button
|
||||
// press or release.
|
||||
//
|
||||
// The mask is a bitwise mask of various ButtonMask values. When a button
|
||||
// is set, it is pressed, when it is unset, it is released.
|
||||
//
|
||||
// See RFC 6143 Section 7.5.5
|
||||
func (c *ClientConn) PointerEvent(mask ButtonMask, x, y uint16) error {
|
||||
var buf bytes.Buffer
|
||||
|
||||
data := []interface{}{
|
||||
uint8(5),
|
||||
uint8(mask),
|
||||
x,
|
||||
y,
|
||||
}
|
||||
|
||||
for _, val := range data {
|
||||
if err := binary.Write(&buf, binary.BigEndian, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := c.conn.Write(buf.Bytes()[0:6]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetEncodings sets the encoding types in which the pixel data can
|
||||
// be sent from the server. After calling this method, the encs slice
|
||||
// given should not be modified.
|
||||
//
|
||||
// See RFC 6143 Section 7.5.2
|
||||
func (c *ClientConn) SetEncodings(encs []common.Encoding) error {
|
||||
data := make([]interface{}, 3+len(encs))
|
||||
data[0] = uint8(2)
|
||||
data[1] = uint8(0)
|
||||
data[2] = uint16(len(encs))
|
||||
|
||||
for i, enc := range encs {
|
||||
data[3+i] = int32(enc.Type())
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
for _, val := range data {
|
||||
if err := binary.Write(&buf, binary.BigEndian, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
dataLength := 4 + (4 * len(encs))
|
||||
if _, err := c.conn.Write(buf.Bytes()[0:dataLength]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Encs = encs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPixelFormat sets the format in which pixel values should be sent
|
||||
// in FramebufferUpdate messages from the server.
|
||||
//
|
||||
// See RFC 6143 Section 7.5.1
|
||||
func (c *ClientConn) SetPixelFormat(format *common.PixelFormat) error {
|
||||
var keyEvent [20]byte
|
||||
keyEvent[0] = 0
|
||||
|
||||
pfBytes, err := writePixelFormat(format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy the pixel format bytes into the proper slice location
|
||||
copy(keyEvent[4:], pfBytes)
|
||||
|
||||
// Send the data down the connection
|
||||
if _, err := c.conn.Write(keyEvent[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reset the color map as according to RFC.
|
||||
var newColorMap common.ColorMap
|
||||
c.ColorMap = newColorMap
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const pvLen = 12 // ProtocolVersion message length.
|
||||
|
||||
func parseProtocolVersion(pv []byte) (uint, uint, error) {
|
||||
var major, minor uint
|
||||
|
||||
if len(pv) < pvLen {
|
||||
return 0, 0, fmt.Errorf("ProtocolVersion message too short (%v < %v)", len(pv), pvLen)
|
||||
}
|
||||
|
||||
l, err := fmt.Sscanf(string(pv), "RFB %d.%d\n", &major, &minor)
|
||||
if l != 2 {
|
||||
return 0, 0, fmt.Errorf("error parsing ProtocolVersion.")
|
||||
}
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
return major, minor, nil
|
||||
}
|
||||
|
||||
func (c *ClientConn) handshake() error {
|
||||
var protocolVersion [pvLen]byte
|
||||
|
||||
// 7.1.1, read the ProtocolVersion message sent by the server.
|
||||
if _, err := io.ReadFull(c.conn, protocolVersion[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
maxMajor, maxMinor, err := parseProtocolVersion(protocolVersion[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maxMajor < 3 {
|
||||
return fmt.Errorf("unsupported major version, less than 3: %d", maxMajor)
|
||||
}
|
||||
if maxMinor < 8 {
|
||||
return fmt.Errorf("unsupported minor version, less than 8: %d", maxMinor)
|
||||
}
|
||||
|
||||
// Respond with the version we will support
|
||||
if _, err = c.conn.Write([]byte("RFB 003.008\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 7.1.2 Security Handshake from server
|
||||
var numSecurityTypes uint8
|
||||
if err = binary.Read(c.conn, binary.BigEndian, &numSecurityTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if numSecurityTypes == 0 {
|
||||
return fmt.Errorf("no security types: %s", c.readErrorReason())
|
||||
}
|
||||
|
||||
securityTypes := make([]uint8, numSecurityTypes)
|
||||
if err = binary.Read(c.conn, binary.BigEndian, &securityTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientSecurityTypes := c.config.Auth
|
||||
if clientSecurityTypes == nil {
|
||||
clientSecurityTypes = []ClientAuth{new(ClientAuthNone)}
|
||||
}
|
||||
|
||||
var auth ClientAuth
|
||||
FindAuth:
|
||||
for _, curAuth := range clientSecurityTypes {
|
||||
for _, securityType := range securityTypes {
|
||||
if curAuth.SecurityType() == securityType {
|
||||
// We use the first matching supported authentication
|
||||
auth = curAuth
|
||||
break FindAuth
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if auth == nil {
|
||||
return fmt.Errorf("no suitable auth schemes found. server supported: %#v", securityTypes)
|
||||
}
|
||||
|
||||
// Respond back with the security type we'll use
|
||||
if err = binary.Write(c.conn, binary.BigEndian, auth.SecurityType()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = auth.Handshake(c.conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 7.1.3 SecurityResult Handshake
|
||||
var securityResult uint32
|
||||
if err = binary.Read(c.conn, binary.BigEndian, &securityResult); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if securityResult == 1 {
|
||||
return fmt.Errorf("security handshake failed: %s", c.readErrorReason())
|
||||
}
|
||||
|
||||
// 7.3.1 ClientInit
|
||||
var sharedFlag uint8 = 1
|
||||
if c.config.Exclusive {
|
||||
sharedFlag = 0
|
||||
}
|
||||
|
||||
if err = binary.Write(c.conn, binary.BigEndian, sharedFlag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 7.3.2 ServerInit
|
||||
if err = binary.Read(c.conn, binary.BigEndian, &c.FrameBufferWidth); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = binary.Read(c.conn, binary.BigEndian, &c.FrameBufferHeight); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read the pixel format
|
||||
if err = readPixelFormat(c.conn, &c.PixelFormat); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var nameLength uint32
|
||||
if err = binary.Read(c.conn, binary.BigEndian, &nameLength); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nameBytes := make([]uint8, nameLength)
|
||||
if err = binary.Read(c.conn, binary.BigEndian, &nameBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.DesktopName = string(nameBytes)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// mainLoop reads messages sent from the server and routes them to the
|
||||
// proper channels for users of the client to read.
|
||||
func (c *ClientConn) mainLoop() {
|
||||
defer c.Close()
|
||||
rec := listeners.NewRecorder("/Users/amitbet/recording.rbs", c.DesktopName, c.FrameBufferWidth, c.FrameBufferHeight)
|
||||
|
||||
reader := &common.RfbReadHelper{Reader: c.conn, Listener: rec}
|
||||
// Build the map of available server messages
|
||||
typeMap := make(map[uint8]common.ServerMessage)
|
||||
|
||||
defaultMessages := []common.ServerMessage{
|
||||
new(FramebufferUpdateMessage),
|
||||
new(SetColorMapEntriesMessage),
|
||||
new(BellMessage),
|
||||
new(ServerCutTextMessage),
|
||||
}
|
||||
|
||||
for _, msg := range defaultMessages {
|
||||
typeMap[msg.Type()] = msg
|
||||
}
|
||||
|
||||
if c.config.ServerMessages != nil {
|
||||
for _, msg := range c.config.ServerMessages {
|
||||
typeMap[msg.Type()] = msg
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
var messageType uint8
|
||||
if err := binary.Read(c.conn, binary.BigEndian, &messageType); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
msg, ok := typeMap[messageType]
|
||||
if !ok {
|
||||
// Unsupported message type! Bad!
|
||||
break
|
||||
}
|
||||
reader.SendMessageSeparator(common.ServerMessageType(messageType))
|
||||
reader.PublishBytes([]byte{byte(messageType)})
|
||||
|
||||
parsedMsg, err := msg.Read(c, reader)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if c.config.ServerMessageCh == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
c.config.ServerMessageCh <- parsedMsg
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ClientConn) readErrorReason() string {
|
||||
var reasonLen uint32
|
||||
if err := binary.Read(c.conn, binary.BigEndian, &reasonLen); err != nil {
|
||||
return "<error>"
|
||||
}
|
||||
|
||||
reason := make([]uint8, reasonLen)
|
||||
if err := binary.Read(c.conn, binary.BigEndian, &reason); err != nil {
|
||||
return "<error>"
|
||||
}
|
||||
|
||||
return string(reason)
|
||||
}
|
114
client/client_auth.go
Normal file
114
client/client_auth.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"crypto/des"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
|
||||
// ClientAuthNone is the "none" authentication. See 7.2.1
|
||||
type ClientAuthNone byte
|
||||
|
||||
func (*ClientAuthNone) SecurityType() uint8 {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (*ClientAuthNone) Handshake(net.Conn) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PasswordAuth is VNC authentication, 7.2.2
|
||||
type PasswordAuth struct {
|
||||
Password string
|
||||
}
|
||||
|
||||
func (p *PasswordAuth) SecurityType() uint8 {
|
||||
return 2
|
||||
}
|
||||
|
||||
func (p *PasswordAuth) Handshake(c net.Conn) error {
|
||||
randomValue := make([]uint8, 16)
|
||||
if err := binary.Read(c, binary.BigEndian, &randomValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
crypted, err := p.encrypt(p.Password, randomValue)
|
||||
|
||||
if (err != nil) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := binary.Write(c, binary.BigEndian, &crypted); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PasswordAuth) reverseBits(b byte) byte {
|
||||
var reverse = [256]int{
|
||||
0, 128, 64, 192, 32, 160, 96, 224,
|
||||
16, 144, 80, 208, 48, 176, 112, 240,
|
||||
8, 136, 72, 200, 40, 168, 104, 232,
|
||||
24, 152, 88, 216, 56, 184, 120, 248,
|
||||
4, 132, 68, 196, 36, 164, 100, 228,
|
||||
20, 148, 84, 212, 52, 180, 116, 244,
|
||||
12, 140, 76, 204, 44, 172, 108, 236,
|
||||
28, 156, 92, 220, 60, 188, 124, 252,
|
||||
2, 130, 66, 194, 34, 162, 98, 226,
|
||||
18, 146, 82, 210, 50, 178, 114, 242,
|
||||
10, 138, 74, 202, 42, 170, 106, 234,
|
||||
26, 154, 90, 218, 58, 186, 122, 250,
|
||||
6, 134, 70, 198, 38, 166, 102, 230,
|
||||
22, 150, 86, 214, 54, 182, 118, 246,
|
||||
14, 142, 78, 206, 46, 174, 110, 238,
|
||||
30, 158, 94, 222, 62, 190, 126, 254,
|
||||
1, 129, 65, 193, 33, 161, 97, 225,
|
||||
17, 145, 81, 209, 49, 177, 113, 241,
|
||||
9, 137, 73, 201, 41, 169, 105, 233,
|
||||
25, 153, 89, 217, 57, 185, 121, 249,
|
||||
5, 133, 69, 197, 37, 165, 101, 229,
|
||||
21, 149, 85, 213, 53, 181, 117, 245,
|
||||
13, 141, 77, 205, 45, 173, 109, 237,
|
||||
29, 157, 93, 221, 61, 189, 125, 253,
|
||||
3, 131, 67, 195, 35, 163, 99, 227,
|
||||
19, 147, 83, 211, 51, 179, 115, 243,
|
||||
11, 139, 75, 203, 43, 171, 107, 235,
|
||||
27, 155, 91, 219, 59, 187, 123, 251,
|
||||
7, 135, 71, 199, 39, 167, 103, 231,
|
||||
23, 151, 87, 215, 55, 183, 119, 247,
|
||||
15, 143, 79, 207, 47, 175, 111, 239,
|
||||
31, 159, 95, 223, 63, 191, 127, 255,
|
||||
}
|
||||
|
||||
return byte(reverse[int(b)])
|
||||
}
|
||||
|
||||
func (p *PasswordAuth) encrypt(key string, bytes []byte) ([]byte, error) {
|
||||
keyBytes := []byte{0,0,0,0,0,0,0,0}
|
||||
|
||||
if len(key) > 8 {
|
||||
key = key[:8]
|
||||
}
|
||||
|
||||
for i := 0; i < len(key); i++ {
|
||||
keyBytes[i] = p.reverseBits(key[i])
|
||||
}
|
||||
|
||||
block, err := des.NewCipher(keyBytes)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result1 := make([]byte, 8)
|
||||
block.Encrypt(result1, bytes)
|
||||
result2 := make([]byte, 8)
|
||||
block.Encrypt(result2, bytes[8:])
|
||||
|
||||
crypted := append(result1, result2...)
|
||||
|
||||
return crypted, nil
|
||||
}
|
169
client/client_auth_test.go
Normal file
169
client/client_auth_test.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"net"
|
||||
"time"
|
||||
"bytes"
|
||||
)
|
||||
|
||||
type fakeNetConnection struct {
|
||||
DataToSend []byte
|
||||
Test *testing.T
|
||||
ExpectData []byte
|
||||
Finished bool
|
||||
Matched bool
|
||||
}
|
||||
|
||||
func (fc fakeNetConnection) Read(b []byte) (n int, err error) {
|
||||
for i := 0; i < 16; i++ {
|
||||
b[i] = fc.DataToSend[i]
|
||||
}
|
||||
|
||||
fc.Finished = false
|
||||
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (fc *fakeNetConnection) Write(b []byte) (n int, err error) {
|
||||
fc.Matched = bytes.Equal(b, fc.ExpectData)
|
||||
fc.Finished = true
|
||||
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (fc *fakeNetConnection) Close() error { return nil; }
|
||||
func (fc *fakeNetConnection) LocalAddr() net.Addr { return nil; }
|
||||
func (fc *fakeNetConnection) RemoteAddr() net.Addr { return nil; }
|
||||
func (fc *fakeNetConnection) SetDeadline(t time.Time) error { return nil; }
|
||||
func (fc *fakeNetConnection) SetReadDeadline(t time.Time) error { return nil; }
|
||||
func (fc *fakeNetConnection) SetWriteDeadline(t time.Time) error { return nil; }
|
||||
|
||||
func TestClientAuthNone_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = new(ClientAuthNone)
|
||||
if _, ok := raw.(ClientAuth); !ok {
|
||||
t.Fatal("ClientAuthNone doesn't implement ClientAuth")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientAuthPasswordSuccess_Impl(t *testing.T) {
|
||||
// Values ripped using WireShark
|
||||
randomValue := []byte{
|
||||
0xa4,
|
||||
0x51,
|
||||
0x3f,
|
||||
0xa5,
|
||||
0x1f,
|
||||
0x87,
|
||||
0x06,
|
||||
0x10,
|
||||
0xa4,
|
||||
0x5f,
|
||||
0xae,
|
||||
0xbf,
|
||||
0x4d,
|
||||
0xac,
|
||||
0x12,
|
||||
0x22,
|
||||
}
|
||||
|
||||
expectedResponse := []byte{
|
||||
0x71,
|
||||
0xe4,
|
||||
0x41,
|
||||
0x30,
|
||||
0x43,
|
||||
0x65,
|
||||
0x4e,
|
||||
0x39,
|
||||
0xda,
|
||||
0x6d,
|
||||
0x49,
|
||||
0x93,
|
||||
0x43,
|
||||
0xf6,
|
||||
0x5e,
|
||||
0x29,
|
||||
}
|
||||
|
||||
raw := PasswordAuth{Password: "Ch_#!T@8"}
|
||||
|
||||
// Only about 12 hours into Go at the moment...
|
||||
// if _, ok := raw.(ClientAuth); !ok {
|
||||
// t.Fatal("PasswordAuth doesn't implement ClientAuth")
|
||||
// }
|
||||
|
||||
conn := &fakeNetConnection{DataToSend: randomValue, ExpectData: expectedResponse, Test: t}
|
||||
err := raw.Handshake(conn)
|
||||
|
||||
if (err != nil) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !conn.Matched {
|
||||
t.Fatal("PasswordAuth didn't pass the right response back to the wire")
|
||||
}
|
||||
|
||||
if !conn.Finished {
|
||||
t.Fatal("PasswordAuth didn't complete properly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientAuthPasswordReject_Impl(t *testing.T) {
|
||||
// Values ripped using WireShark
|
||||
randomValue := []byte{
|
||||
0xa4,
|
||||
0x51,
|
||||
0x3f,
|
||||
0xa5,
|
||||
0x1f,
|
||||
0x87,
|
||||
0x06,
|
||||
0x10,
|
||||
0xa4,
|
||||
0x5f,
|
||||
0xae,
|
||||
0xbf,
|
||||
0x4d,
|
||||
0xac,
|
||||
0x12,
|
||||
0x22,
|
||||
}
|
||||
|
||||
expectedResponse := []byte{
|
||||
0x71,
|
||||
0xe4,
|
||||
0x41,
|
||||
0x30,
|
||||
0x43,
|
||||
0x65,
|
||||
0x4e,
|
||||
0x39,
|
||||
0xda,
|
||||
0x6d,
|
||||
0x49,
|
||||
0x93,
|
||||
0x43,
|
||||
0xf6,
|
||||
0x5e,
|
||||
0x29,
|
||||
}
|
||||
|
||||
raw := PasswordAuth{Password: "Ch_#!T@"}
|
||||
|
||||
conn := &fakeNetConnection{DataToSend: randomValue, ExpectData: expectedResponse, Test: t}
|
||||
err := raw.Handshake(conn)
|
||||
|
||||
if (err != nil) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if conn.Matched {
|
||||
t.Fatal("PasswordAuth didn't pass the right response back to the wire")
|
||||
}
|
||||
|
||||
if !conn.Finished {
|
||||
t.Fatal("PasswordAuth didn't complete properly")
|
||||
}
|
||||
}
|
95
client/client_test.go
Normal file
95
client/client_test.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func newMockServer(t *testing.T, version string) string {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("error listening: %s", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer ln.Close()
|
||||
c, err := ln.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("error accepting conn: %s", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
_, err = c.Write([]byte(fmt.Sprintf("RFB %s\n", version)))
|
||||
if err != nil {
|
||||
t.Fatal("failed writing version")
|
||||
}
|
||||
}()
|
||||
|
||||
return ln.Addr().String()
|
||||
}
|
||||
|
||||
func TestClient_LowMajorVersion(t *testing.T) {
|
||||
nc, err := net.Dial("tcp", newMockServer(t, "002.009"))
|
||||
if err != nil {
|
||||
t.Fatalf("error connecting to mock server: %s", err)
|
||||
}
|
||||
|
||||
_, err = Client(nc, &ClientConfig{})
|
||||
if err == nil {
|
||||
t.Fatal("error expected")
|
||||
}
|
||||
|
||||
if err.Error() != "unsupported major version, less than 3: 2" {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_LowMinorVersion(t *testing.T) {
|
||||
nc, err := net.Dial("tcp", newMockServer(t, "003.007"))
|
||||
if err != nil {
|
||||
t.Fatalf("error connecting to mock server: %s", err)
|
||||
}
|
||||
|
||||
_, err = Client(nc, &ClientConfig{})
|
||||
if err == nil {
|
||||
t.Fatal("error expected")
|
||||
}
|
||||
|
||||
if err.Error() != "unsupported minor version, less than 8: 7" {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseProtocolVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
proto []byte
|
||||
major, minor uint
|
||||
isErr bool
|
||||
}{
|
||||
// Valid ProtocolVersion messages.
|
||||
{[]byte{82, 70, 66, 32, 48, 48, 51, 46, 48, 48, 56, 10}, 3, 8, false}, // RFB 003.008\n
|
||||
{[]byte{82, 70, 66, 32, 48, 48, 51, 46, 56, 56, 57, 10}, 3, 889, false}, // RFB 003.889\n -- OS X 10.10.3
|
||||
{[]byte{82, 70, 66, 32, 48, 48, 48, 46, 48, 48, 48, 10}, 0, 0, false}, // RFB 000.0000\n
|
||||
// Invalid messages.
|
||||
{[]byte{82, 70, 66, 32, 51, 46, 56, 10}, 0, 0, true}, // RFB 3.8\n -- too short; not zero padded
|
||||
{[]byte{82, 70, 66, 10}, 0, 0, true}, // RFB\n -- too short
|
||||
{[]byte{}, 0, 0, true}, // (empty) -- too short
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
major, minor, err := parseProtocolVersion(tt.proto)
|
||||
if err != nil && !tt.isErr {
|
||||
t.Fatalf("parseProtocolVersion(%v) unexpected error %v", tt.proto, err)
|
||||
}
|
||||
if err == nil && tt.isErr {
|
||||
t.Fatalf("parseProtocolVersion(%v) expected error", tt.proto)
|
||||
}
|
||||
if major != tt.major {
|
||||
t.Errorf("parseProtocolVersion(%v) major = %v, want %v", tt.proto, major, tt.major)
|
||||
}
|
||||
if major != tt.major {
|
||||
t.Errorf("parseProtocolVersion(%v) minor = %v, want %v", tt.proto, minor, tt.minor)
|
||||
}
|
||||
}
|
||||
}
|
6
client/color.go
Normal file
6
client/color.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package client
|
||||
|
||||
// Color represents a single color in a color map.
|
||||
type Color struct {
|
||||
R, G, B uint16
|
||||
}
|
BIN
client/debug.test
Normal file
BIN
client/debug.test
Normal file
Binary file not shown.
136
client/pixel-format.go
Normal file
136
client/pixel-format.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"vncproxy/common"
|
||||
)
|
||||
|
||||
func readPixelFormat(r io.Reader, result *common.PixelFormat) error {
|
||||
var rawPixelFormat [16]byte
|
||||
if _, err := io.ReadFull(r, rawPixelFormat[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var pfBoolByte uint8
|
||||
brPF := bytes.NewReader(rawPixelFormat[:])
|
||||
if err := binary.Read(brPF, binary.BigEndian, &result.BPP); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := binary.Read(brPF, binary.BigEndian, &result.Depth); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := binary.Read(brPF, binary.BigEndian, &pfBoolByte); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if pfBoolByte != 0 {
|
||||
// Big endian is true
|
||||
result.BigEndian = true
|
||||
}
|
||||
|
||||
if err := binary.Read(brPF, binary.BigEndian, &pfBoolByte); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if pfBoolByte != 0 {
|
||||
// True Color is true. So we also have to read all the color max & shifts.
|
||||
result.TrueColor = true
|
||||
|
||||
if err := binary.Read(brPF, binary.BigEndian, &result.RedMax); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := binary.Read(brPF, binary.BigEndian, &result.GreenMax); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := binary.Read(brPF, binary.BigEndian, &result.BlueMax); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := binary.Read(brPF, binary.BigEndian, &result.RedShift); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := binary.Read(brPF, binary.BigEndian, &result.GreenShift); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := binary.Read(brPF, binary.BigEndian, &result.BlueShift); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writePixelFormat(format *common.PixelFormat) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
// Byte 1
|
||||
if err := binary.Write(&buf, binary.BigEndian, format.BPP); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Byte 2
|
||||
if err := binary.Write(&buf, binary.BigEndian, format.Depth); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var boolByte byte
|
||||
if format.BigEndian {
|
||||
boolByte = 1
|
||||
} else {
|
||||
boolByte = 0
|
||||
}
|
||||
|
||||
// Byte 3 (BigEndian)
|
||||
if err := binary.Write(&buf, binary.BigEndian, boolByte); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if format.TrueColor {
|
||||
boolByte = 1
|
||||
} else {
|
||||
boolByte = 0
|
||||
}
|
||||
|
||||
// Byte 4 (TrueColor)
|
||||
if err := binary.Write(&buf, binary.BigEndian, boolByte); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If we have true color enabled then we have to fill in the rest of the
|
||||
// structure with the color values.
|
||||
if format.TrueColor {
|
||||
if err := binary.Write(&buf, binary.BigEndian, format.RedMax); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Write(&buf, binary.BigEndian, format.GreenMax); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Write(&buf, binary.BigEndian, format.BlueMax); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Write(&buf, binary.BigEndian, format.RedShift); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Write(&buf, binary.BigEndian, format.GreenShift); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Write(&buf, binary.BigEndian, format.BlueShift); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return buf.Bytes()[0:16], nil
|
||||
}
|
16
client/pointer.go
Normal file
16
client/pointer.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package client
|
||||
|
||||
// ButtonMask represents a mask of pointer presses/releases.
|
||||
type ButtonMask uint8
|
||||
|
||||
// All available button mask components.
|
||||
const (
|
||||
ButtonLeft ButtonMask = 1 << iota
|
||||
ButtonMiddle
|
||||
ButtonRight
|
||||
Button4
|
||||
Button5
|
||||
Button6
|
||||
Button7
|
||||
Button8
|
||||
)
|
212
client/server-messages.go
Normal file
212
client/server-messages.go
Normal file
@@ -0,0 +1,212 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"vncproxy/common"
|
||||
"vncproxy/encodings"
|
||||
)
|
||||
|
||||
// FramebufferUpdateMessage consists of a sequence of rectangles of
|
||||
// pixel data that the client should put into its framebuffer.
|
||||
type FramebufferUpdateMessage struct {
|
||||
Rectangles []common.Rectangle
|
||||
}
|
||||
|
||||
func (m *FramebufferUpdateMessage) String() string {
|
||||
str := fmt.Sprintf("FramebufferUpdateMessage (type=%d) Rects: \n", m.Type())
|
||||
for _, rect := range m.Rectangles {
|
||||
str += rect.String() + "\n"
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
func (*FramebufferUpdateMessage) Type() uint8 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (fbm *FramebufferUpdateMessage) Read(c common.IClientConn, r *common.RfbReadHelper) (common.ServerMessage, error) {
|
||||
|
||||
// Read off the padding
|
||||
var padding [1]byte
|
||||
if _, err := io.ReadFull(r, padding[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var numRects uint16
|
||||
if err := binary.Read(r, binary.BigEndian, &numRects); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Build the map of encodings supported
|
||||
encMap := make(map[int32]common.Encoding)
|
||||
for _, enc := range c.Encodings() {
|
||||
encMap[enc.Type()] = enc
|
||||
}
|
||||
|
||||
// We must always support the raw encoding
|
||||
rawEnc := new(encodings.RawEncoding)
|
||||
encMap[rawEnc.Type()] = rawEnc
|
||||
fmt.Printf("numrects= %d\n", numRects)
|
||||
|
||||
rects := make([]common.Rectangle, numRects)
|
||||
for i := uint16(0); i < numRects; i++ {
|
||||
fmt.Printf("###############rect################: %d\n", i)
|
||||
|
||||
var encodingType int32
|
||||
r.SendRectSeparator(-1)
|
||||
rect := &rects[i]
|
||||
data := []interface{}{
|
||||
&rect.X,
|
||||
&rect.Y,
|
||||
&rect.Width,
|
||||
&rect.Height,
|
||||
&encodingType,
|
||||
}
|
||||
|
||||
for _, val := range data {
|
||||
if err := binary.Read(r, binary.BigEndian, val); err != nil {
|
||||
fmt.Printf("err: %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
jBytes, _ := json.Marshal(data)
|
||||
|
||||
fmt.Printf("rect hdr data: %s\n", string(jBytes))
|
||||
//fmt.Printf(" encoding type: %d", encodingType)
|
||||
enc, ok := encMap[encodingType]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported encoding type: %d\n", encodingType)
|
||||
}
|
||||
|
||||
var err error
|
||||
rect.Enc, err = enc.Read(c.CurrentPixelFormat(), rect, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &FramebufferUpdateMessage{rects}, nil
|
||||
}
|
||||
|
||||
// SetColorMapEntriesMessage is sent by the server to set values into
|
||||
// the color map. This message will automatically update the color map
|
||||
// for the associated connection, but contains the color change data
|
||||
// if the consumer wants to read it.
|
||||
//
|
||||
// See RFC 6143 Section 7.6.2
|
||||
type SetColorMapEntriesMessage struct {
|
||||
FirstColor uint16
|
||||
Colors []common.Color
|
||||
}
|
||||
|
||||
func (m *SetColorMapEntriesMessage) String() string {
|
||||
return fmt.Sprintf("SetColorMapEntriesMessage (type=%d) first:%d colors: %v: ", m.Type(), m.FirstColor, m.Colors)
|
||||
}
|
||||
|
||||
func (*SetColorMapEntriesMessage) Type() uint8 {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (*SetColorMapEntriesMessage) Read(c common.IClientConn, r *common.RfbReadHelper) (common.ServerMessage, error) {
|
||||
// Read off the padding
|
||||
var padding [1]byte
|
||||
if _, err := io.ReadFull(r, padding[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result SetColorMapEntriesMessage
|
||||
if err := binary.Read(r, binary.BigEndian, &result.FirstColor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var numColors uint16
|
||||
if err := binary.Read(r, binary.BigEndian, &numColors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result.Colors = make([]common.Color, numColors)
|
||||
for i := uint16(0); i < numColors; i++ {
|
||||
|
||||
color := &result.Colors[i]
|
||||
data := []interface{}{
|
||||
&color.R,
|
||||
&color.G,
|
||||
&color.B,
|
||||
}
|
||||
|
||||
for _, val := range data {
|
||||
if err := binary.Read(r, binary.BigEndian, val); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
cmap := c.CurrentColorMap()
|
||||
// Update the connection's color map
|
||||
cmap[result.FirstColor+i] = *color
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// Bell signals that an audible bell should be made on the client.
|
||||
//
|
||||
// See RFC 6143 Section 7.6.3
|
||||
type BellMessage byte
|
||||
|
||||
func (m *BellMessage) String() string {
|
||||
return fmt.Sprintf("BellMessage (type=%d)", m.Type())
|
||||
}
|
||||
|
||||
func (*BellMessage) Type() uint8 {
|
||||
return 2
|
||||
}
|
||||
|
||||
func (*BellMessage) Read(common.IClientConn, *common.RfbReadHelper) (common.ServerMessage, error) {
|
||||
return new(BellMessage), nil
|
||||
}
|
||||
|
||||
// ServerCutTextMessage indicates the server has new text in the cut buffer.
|
||||
//
|
||||
// See RFC 6143 Section 7.6.4
|
||||
type ServerCutTextMessage struct {
|
||||
Text string
|
||||
}
|
||||
|
||||
func (m *ServerCutTextMessage) String() string {
|
||||
return fmt.Sprintf("ServerCutTextMessage (type=%d)", m.Type())
|
||||
}
|
||||
|
||||
func (*ServerCutTextMessage) Type() uint8 {
|
||||
return 3
|
||||
}
|
||||
|
||||
func (*ServerCutTextMessage) Read(conn common.IClientConn, r *common.RfbReadHelper) (common.ServerMessage, error) {
|
||||
//reader := common.RfbReadHelper{Reader: r}
|
||||
|
||||
// Read off the padding
|
||||
var padding [3]byte
|
||||
if _, err := io.ReadFull(r, padding[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
textLength, err := r.ReadUint32()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
textBytes, err := r.ReadBytes(int(textLength))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//var textLength uint32
|
||||
// if err := binary.Read(r, binary.BigEndian, &textLength); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// textBytes := make([]uint8, textLength)
|
||||
// if err := binary.Read(r, binary.BigEndian, &textBytes); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return &ServerCutTextMessage{string(textBytes)}, nil
|
||||
}
|
Reference in New Issue
Block a user