diff --git a/main.go b/main.go new file mode 100644 index 0000000..2340df1 --- /dev/null +++ b/main.go @@ -0,0 +1,61 @@ +package main + +import ( + "fmt" + "net" + "os" + "time" + "vncproxy/vnc" +) + +func main() { + fmt.Println("") + //nc, err := net.Dial("tcp", "192.168.1.101:5903") + nc, err := net.Dial("tcp", "localhost:5903") + + if err != nil { + fmt.Printf(";error connecting to vnc server: %s", err) + } + var noauth vnc.ClientAuthNone + authArr := []vnc.ClientAuth{&vnc.PasswordAuth{Password: "Ch_#!T@8"}, &noauth} + + vncSrvMessagesChan := make(chan vnc.ServerMessage) + clientConn, err := vnc.Client(nc, &vnc.ClientConfig{Auth: authArr, ServerMessageCh: vncSrvMessagesChan, Exclusive: true}) + if err != nil { + fmt.Printf("error creating client: %s", err) + } + // err = clientConn.FramebufferUpdateRequest(false, 0, 0, 1024, 768) + // if err != nil { + // fmt.Printf("error requesting fb update: %s\n", err) + // } + + tight := vnc.TightEncoding{} + //rre := vnc.RREEncoding{} + //zlib := vnc.ZLibEncoding{} + //zrle := vnc.ZRLEEncoding{} + cpyRect := vnc.CopyRectEncoding{} + //hextile := vnc.HextileEncoding{} + file, _ := os.OpenFile("stam.bin", os.O_CREATE|os.O_RDWR, 0755) + defer file.Close() + + tight.SetOutput(file) + clientConn.SetEncodings([]vnc.Encoding{&cpyRect, &tight}) + + go func() { + for { + err = clientConn.FramebufferUpdateRequest(true, 0, 0, 1280, 800) + if err != nil { + fmt.Printf("error requesting fb update: %s\n", err) + } + time.Sleep(2 * time.Second) + } + }() + + //go func() { + for msg := range vncSrvMessagesChan { + fmt.Printf("message type: %d, content: %v\n", msg.Type(), msg) + } + //}() + + //clientConn.Close() +} diff --git a/vnc/Logger.go b/vnc/Logger.go new file mode 100644 index 0000000..389ee0b --- /dev/null +++ b/vnc/Logger.go @@ -0,0 +1,14 @@ +package vnc + +type Logger interface { + Debug(v ...interface{}) + Debugf(format string, v ...interface{}) + Info(v ...interface{}) + Infof(format string, v ...interface{}) + Warn(v ...interface{}) + Warnf(format string, v ...interface{}) + Error(v ...interface{}) + Errorf(format string, v ...interface{}) + Fatal(v ...interface{}) + Fatalf(format string, v ...interface{}) +} diff --git a/vnc/README.md b/vnc/README.md new file mode 100644 index 0000000..cb0575b --- /dev/null +++ b/vnc/README.md @@ -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 diff --git a/vnc/client-conn.go b/vnc/client-conn.go new file mode 100644 index 0000000..6fc55a8 --- /dev/null +++ b/vnc/client-conn.go @@ -0,0 +1,504 @@ +package vnc + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "net" + "unicode" +) + +// A ServerMessage implements a message sent from the server to the client. +type ServerMessage interface { + // The type of the message that is sent down on the wire. + Type() uint8 + String() string + // Read reads the contents of the message from the reader. At the point + // this is called, the message type has already been read from the reader. + // This should return a new ServerMessage that is the appropriate type. + Read(*ClientConn, io.Reader) (ServerMessage, error) +} + +// 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 + output io.Writer + passThrough bool + + //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 [256]Color + + // Encodings supported by the client. This should not be modified + // directly. Instead, SetEncodings should be used. + Encs []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 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<- 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 []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() +} + +// 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 []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 *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 [256]Color + 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() + + // Build the map of available server messages + typeMap := make(map[uint8]ServerMessage) + + defaultMessages := []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 + } + + parsedMsg, err := msg.Read(c, c.conn) + 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 "" + } + + reason := make([]uint8, reasonLen) + if err := binary.Read(c.conn, binary.BigEndian, &reason); err != nil { + return "" + } + + return string(reason) +} diff --git a/vnc/client.go b/vnc/client.go new file mode 100644 index 0000000..e46d86c --- /dev/null +++ b/vnc/client.go @@ -0,0 +1,75 @@ +// Package vnc implements a VNC client. +// +// References: +// [PROTOCOL]: http://tools.ietf.org/html/rfc6143 +package vnc + +import ( + "encoding/binary" + "io" +) + +// type DataSource struct { +// conn io.Reader +// output io.Writer +// passThrough bool +// PixelFormat PixelFormat +// }Color + +func (d *ClientConn) readBytes(count int) ([]byte, error) { + buff := make([]byte, count) + + _, err := io.ReadFull(d.conn, buff) + if err != nil { + //if err := binary.Read(d.conn, binary.BigEndian, &buff); err != nil { + return nil, err + } + return buff, nil +} + +func (d *ClientConn) readUint8() (uint8, error) { + var myUint uint8 + if err := binary.Read(d.conn, binary.BigEndian, &myUint); err != nil { + return 0, err + } + //fmt.Printf("myUint=%d", myUint) + return myUint, nil +} +func (d *ClientConn) readUint16() (uint16, error) { + var myUint uint16 + if err := binary.Read(d.conn, binary.BigEndian, &myUint); err != nil { + return 0, err + } + //fmt.Printf("myUint=%d", myUint) + return myUint, nil +} +func (d *ClientConn) readUint32() (uint32, error) { + var myUint uint32 + if err := binary.Read(d.conn, binary.BigEndian, &myUint); err != nil { + return 0, err + } + //fmt.Printf("myUint=%d", myUint) + return myUint, nil +} +func (d *ClientConn) readCompactLen() (int, error) { + var err error + part, err := d.readUint8() + //byteCount := 1 + len := uint32(part & 0x7F) + if (part & 0x80) != 0 { + part, err = d.readUint8() + //byteCount++ + len |= uint32(part&0x7F) << 7 + if (part & 0x80) != 0 { + part, err = d.readUint8() + //byteCount++ + len |= uint32(part&0xFF) << 14 + } + } + + // for i := 0; i < byteCount; i++{ + // rec.writeByte(portion[i]); + // } + + return int(len), err +} diff --git a/vnc/client_auth.go b/vnc/client_auth.go new file mode 100644 index 0000000..89b4bd8 --- /dev/null +++ b/vnc/client_auth.go @@ -0,0 +1,114 @@ +package vnc + +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 +} diff --git a/vnc/client_auth_test.go b/vnc/client_auth_test.go new file mode 100644 index 0000000..0095eed --- /dev/null +++ b/vnc/client_auth_test.go @@ -0,0 +1,169 @@ +package vnc + +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") + } +} \ No newline at end of file diff --git a/vnc/client_test.go b/vnc/client_test.go new file mode 100644 index 0000000..31591b4 --- /dev/null +++ b/vnc/client_test.go @@ -0,0 +1,95 @@ +package vnc + +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) + } + } +} diff --git a/vnc/color.go b/vnc/color.go new file mode 100644 index 0000000..30df467 --- /dev/null +++ b/vnc/color.go @@ -0,0 +1,6 @@ +package vnc + +// Color represents a single color in a color map. +type Color struct { + R, G, B uint16 +} diff --git a/vnc/enc-hextile.go b/vnc/enc-hextile.go new file mode 100644 index 0000000..b545126 --- /dev/null +++ b/vnc/enc-hextile.go @@ -0,0 +1,81 @@ +package vnc + +import "io" + +const ( + HextileRaw = 1 + HextileBackgroundSpecified = 2 + HextileForegroundSpecified = 4 + HextileAnySubrects = 8 + HextileSubrectsColoured = 16 +) + +type HextileEncoding struct { + Colors []Color +} + +func (z *HextileEncoding) Type() int32 { + return 5 +} +func (z *HextileEncoding) Read(conn *ClientConn, rect *Rectangle, r io.Reader) (Encoding, error) { + //conn := &DataSource{conn: conn.c, PixelFormat: conn.PixelFormat} + bytesPerPixel := int(conn.PixelFormat.BPP) / 8 + //buf := make([]byte, bytesPerPixel) + for ty := rect.Y; ty < rect.Y+rect.Height; ty += 16 { + th := 16 + if rect.Y+rect.Height-ty < 16 { + th = int(rect.Y) + int(rect.Height) - int(ty) + } + + for tx := rect.X; tx < rect.X+rect.Width; tx += 16 { + tw := 16 + if rect.X+rect.Width-tx < 16 { + tw = int(rect.X) + int(rect.Width) - int(tx) + } + + //handle Hextile Subrect(tx, ty, tw, th): + subencoding, err := conn.readUint8() + //fmt.Printf("hextile reader tile: (%d,%d) subenc=%d\n", ty, tx, subencoding) + if err != nil { + //fmt.Printf("error in hextile reader: %v\n", err) + return nil, err + } + + if (subencoding & HextileRaw) != 0 { + //ReadRawRect(c, rect, r) + conn.readBytes(tw * th * bytesPerPixel) + //fmt.Printf("hextile reader: HextileRaw\n") + continue + } + if (subencoding & HextileBackgroundSpecified) != 0 { + conn.readBytes(int(bytesPerPixel)) + } + if (subencoding & HextileForegroundSpecified) != 0 { + conn.readBytes(int(bytesPerPixel)) + } + if (subencoding & HextileAnySubrects) == 0 { + //fmt.Printf("hextile reader: no Subrects\n") + continue + } + //fmt.Printf("hextile reader: handling Subrects\n") + nSubrects, err := conn.readUint8() + if err != nil { + return nil, err + } + bufsize := int(nSubrects) * 2 + if (subencoding & HextileSubrectsColoured) != 0 { + bufsize += int(nSubrects) * int(bytesPerPixel) + } + //byte[] buf = new byte[bufsize]; + conn.readBytes(bufsize) + } + } + + // len, _ := readUint32(c.c) + // _, err := readBytes(c.c, int(len)) + + // if err != nil { + // return nil, err + // } + return z, nil +} diff --git a/vnc/enc-raw.go b/vnc/enc-raw.go new file mode 100644 index 0000000..dc67e8a --- /dev/null +++ b/vnc/enc-raw.go @@ -0,0 +1,58 @@ +package vnc + +import "io" + +// RawEncoding is raw pixel data sent by the server. +// +// See RFC 6143 Section 7.7.1 +type RawEncoding struct { + Colors []Color +} + +func (*RawEncoding) Type() int32 { + return 0 +} + + + +func (*RawEncoding) Read(conn *ClientConn, rect *Rectangle, r io.Reader) (Encoding, error) { + //conn := &DataSource{conn: conn.c, PixelFormat: conn.PixelFormat} + + bytesPerPixel := int(conn.PixelFormat.BPP / 8) + //pixelBytes := make([]uint8, bytesPerPixel) + + // var byteOrder binary.ByteOrder = binary.LittleEndian + // if conn.PixelFormat.BigEndian { + // byteOrder = binary.BigEndian + // } + + colors := make([]Color, int(rect.Height)*int(rect.Width)) + + for y := uint16(0); y < rect.Height; y++ { + for x := uint16(0); x < rect.Width; x++ { + if _, err := conn.readBytes(bytesPerPixel); err != nil { + return nil, err + } + + // var rawPixel uint32 + // if conn.PixelFormat.BPP == 8 { + // rawPixel = uint32(pixelBytes[0]) + // } else if conn.PixelFormat.BPP == 16 { + // rawPixel = uint32(byteOrder.Uint16(pixelBytes)) + // } else if conn.PixelFormat.BPP == 32 { + // rawPixel = byteOrder.Uint32(pixelBytes) + // } + + // color := &colors[int(y)*int(rect.Width)+int(x)] + // if conn.PixelFormat.TrueColor { + // color.R = uint16((rawPixel >> conn.PixelFormat.RedShift) & uint32(conn.PixelFormat.RedMax)) + // color.G = uint16((rawPixel >> conn.PixelFormat.GreenShift) & uint32(conn.PixelFormat.GreenMax)) + // color.B = uint16((rawPixel >> conn.PixelFormat.BlueShift) & uint32(conn.PixelFormat.BlueMax)) + // } else { + // *color = conn.ColorMap[rawPixel] + // } + } + } + + return &RawEncoding{colors}, nil +} diff --git a/vnc/enc-rre.go b/vnc/enc-rre.go new file mode 100644 index 0000000..344f3c5 --- /dev/null +++ b/vnc/enc-rre.go @@ -0,0 +1,27 @@ +package vnc + +import "io" + +type RREEncoding struct { + Colors []Color +} + +func (z *RREEncoding) Type() int32 { + return 2 +} +func (z *RREEncoding) Read(conn *ClientConn, rect *Rectangle, r io.Reader) (Encoding, error) { + + bytesPerPixel := int(conn.PixelFormat.BPP / 8) + numOfSubrectangles, _ := conn.readUint32() + + //read whole rect background color + conn.readBytes(bytesPerPixel) + + //read all individual rects (color=BPP + x=16b + y=16b + w=16b + h=16b) + _, err := conn.readBytes(int(numOfSubrectangles) * (bytesPerPixel + 8)) + + if err != nil { + return nil, err + } + return z, nil +} diff --git a/vnc/enc-tight.go b/vnc/enc-tight.go new file mode 100644 index 0000000..d9346b1 --- /dev/null +++ b/vnc/enc-tight.go @@ -0,0 +1,346 @@ +package vnc + +import ( + "errors" + "fmt" + "io" +) + +var TightMinToCompress int = 12 + +const ( + TightExplicitFilter = 0x04 + TightFill = 0x08 + TightJpeg = 0x09 + TightMaxSubencoding = 0x09 + TightFilterCopy = 0x00 + TightFilterPalette = 0x01 + TightFilterGradient = 0x02 +) + +type TightEncoding struct { + output io.Writer + logger Logger +} + +func (t *TightEncoding) SetOutput(output io.Writer) { + t.output = output +} + +func (*TightEncoding) Type() int32 { + return 7 +} + +// func ReadAndRecBytes(conn io.Reader, rec io.Writer, count int) ([]byte, error) { +// buf, err := readBytes(conn, count) +// rec.Write(buf) +// return buf, err +// } +// func ReadAndRecUint8(conn io.Reader, rec io.Writer) (uint8, error) { +// myUint, err := readUint8(conn) +// buf := make([]byte, 1) +// buf[0] = byte(myUint) // cast int8 to byte +// rec.Write(buf) +// return myUint, err +// } + +// func ReadAndRecUint16(conn io.Reader, rec io.Writer) (uint16, error) { +// myUint, err := readUint16(conn) +// buf := make([]byte, 2) +// //buf[0] = byte(myUint) // cast int8 to byte +// //var i int16 = 41 +// //b := make([]byte, 2) +// binary.LittleEndian.PutUint16(buf, uint16(myUint)) + +// rec.Write(buf) +// return myUint, err +// } + +func calcTightBytePerPixel(pf PixelFormat) int { + bytesPerPixel := int(pf.BPP / 8) + + var bytesPerPixelTight int + if 24 == pf.Depth && 32 == pf.BPP { + bytesPerPixelTight = 3 + } else { + bytesPerPixelTight = bytesPerPixel + } + return bytesPerPixelTight +} + +func (t *TightEncoding) Read(conn *ClientConn, rect *Rectangle, reader io.Reader) (Encoding, error) { + bytesPixel := calcTightBytePerPixel(conn.PixelFormat) + + //conn := &DataSource{conn: conn.c, PixelFormat: conn.PixelFormat} + + //var subencoding uint8 + subencoding, err := conn.readUint8() + if err != nil { + fmt.Printf("error in handling tight encoding: %v\n", err) + return nil, err + } + fmt.Printf("bytesPixel= %d, subencoding= %d\n", bytesPixel, subencoding) + // if err := binary.Read(conn.c, binary.BigEndian, &subencoding); err != nil { + // return t, err + // } + + //move it to position (remove zlib flush commands) + compType := subencoding >> 4 & 0x0F + // for stream_id := 0; stream_id < 4; stream_id++ { + // // if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) { + // // tightInflaters[stream_id] = null; + // // } + // subencoding >>= 1 + // } + + fmt.Printf("afterSHL:%d\n", compType) + switch compType { + case TightFill: + fmt.Printf("reading fill size=%d\n", bytesPixel) + //read color + conn.readBytes(int(bytesPixel)) + return t, nil + case TightJpeg: + if conn.PixelFormat.BPP == 8 { + return nil, errors.New("Tight encoding: JPEG is not supported in 8 bpp mode") + } + + len, err := conn.readCompactLen() + if err != nil { + return nil, err + } + fmt.Printf("reading jpeg size=%d\n", len) + conn.readBytes(len) + return t, nil + default: + + if compType > TightJpeg { + fmt.Println("Compression control byte is incorrect!") + } + + handleTightFilters(subencoding, conn, rect, reader) + return t, nil + } +} + +func handleTightFilters(subencoding uint8, conn *ClientConn, rect *Rectangle, reader io.Reader) { + var FILTER_ID_MASK uint8 = 0x40 + //var STREAM_ID_MASK uint8 = 0x30 + + //decoderId := (subencoding & STREAM_ID_MASK) >> 4 + var filterid uint8 + var err error + + if (subencoding & FILTER_ID_MASK) > 0 { // filter byte presence + filterid, err = conn.readUint8() + if err != nil { + fmt.Printf("error in handling tight encoding, reading filterid: %v\n", err) + return + } + fmt.Printf("read filter: %d\n", filterid) + } + + //var numColors uint8 + bytesPixel := calcTightBytePerPixel(conn.PixelFormat) + + fmt.Printf("filter: %d\n", filterid) + // if rfb.rec != null { + // rfb.rec.writeByte(filter_id) + // } + lengthCurrentbpp := int(bytesPixel) * int(rect.Width) * int(rect.Height) + + switch filterid { + case TightFilterPalette: //PALETTE_FILTER + + colorCount, err := conn.readUint8() + paletteSize := colorCount + 1 // add one more + fmt.Printf("----PALETTE_FILTER: paletteSize=%d bytesPixel=%d\n", paletteSize, bytesPixel) + //complete palette + conn.readBytes(int(paletteSize) * bytesPixel) + + var dataLength int + if paletteSize == 2 { + dataLength = int(rect.Height) * ((int(rect.Width) + 7) / 8) + } else { + dataLength = int(rect.Width * rect.Height) + } + _, err = readTightData(conn, dataLength) + if err != nil { + fmt.Printf("error in handling tight encoding, Reading Palette: %v\n", err) + return + } + case TightFilterGradient: //GRADIENT_FILTER + fmt.Printf("----GRADIENT_FILTER: bytesPixel=%d\n", bytesPixel) + //useGradient = true + fmt.Printf("usegrad: %d\n", filterid) + readTightData(conn, lengthCurrentbpp) + case TightFilterCopy: //BASIC_FILTER + fmt.Printf("----BASIC_FILTER: bytesPixel=%d\n", bytesPixel) + readTightData(conn, lengthCurrentbpp) + default: + fmt.Printf("Bad tight filter id: %d\n", filterid) + return + } + + //////////// + + // if numColors == 0 && bytesPixel == 4 { + // rowSize1 *= 3 + // } + // rowSize := (int(rect.Width)*bitsPixel + 7) / 8 + // dataSize := int(rect.Height) * rowSize + + // dataSize1 := rect.Height * rowSize1 + // fmt.Printf("datasize: %d, origDatasize: %d", dataSize, dataSize1) + // // Read, optionally uncompress and decode data. + // if int(dataSize1) < TightMinToCompress { + // // Data size is small - not compressed with zlib. + // if numColors != 0 { + // // Indexed colors. + // //indexedData := make([]byte, dataSize) + // readBytes(conn.c, int(dataSize1)) + // //readFully(indexedData); + // // if (rfb.rec != null) { + // // rfb.rec.write(indexedData); + // // } + // // if (numColors == 2) { + // // // Two colors. + // // if (bytesPixel == 1) { + // // decodeMonoData(x, y, w, h, indexedData, palette8); + // // } else { + // // decodeMonoData(x, y, w, h, indexedData, palette24); + // // } + // // } else { + // // // 3..255 colors (assuming bytesPixel == 4). + // // int i = 0; + // // for (int dy = y; dy < y + h; dy++) { + // // for (int dx = x; dx < x + w; dx++) { + // // pixels24[dy * rfb.framebufferWidth + dx] = palette24[indexedData[i++] & 0xFF]; + // // } + // // } + // // } + // } else if useGradient { + // // "Gradient"-processed data + // //buf := make ( []byte,w * h * 3); + // dataByteCount := int(3) * int(rect.Width) * int(rect.Height) + // readBytes(conn.c, dataByteCount) + // // rfb.readFully(buf); + // // if (rfb.rec != null) { + // // rfb.rec.write(buf); + // // } + // // decodeGradientData(x, y, w, h, buf); + // } else { + // // Raw truecolor data. + // dataByteCount := int(bytesPixel) * int(rect.Width) * int(rect.Height) + // readBytes(conn.c, dataByteCount) + + // // if (bytesPixel == 1) { + // // for (int dy = y; dy < y + h; dy++) { + + // // rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w); + // // if (rfb.rec != null) { + // // rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w); + // // } + // // } + // // } else { + // // byte[] buf = new byte[w * 3]; + // // int i, offset; + // // for (int dy = y; dy < y + h; dy++) { + // // rfb.readFully(buf); + // // if (rfb.rec != null) { + // // rfb.rec.write(buf); + // // } + // // offset = dy * rfb.framebufferWidth + x; + // // for (i = 0; i < w; i++) { + // // pixels24[offset + i] = (buf[i * 3] & 0xFF) << 16 | (buf[i * 3 + 1] & 0xFF) << 8 | (buf[i * 3 + 2] & 0xFF); + // // } + // // } + // // } + // } + // } else { + // // Data was compressed with zlib. + // zlibDataLen, err := readCompactLen(conn.c) + // fmt.Printf("compactlen=%d\n", zlibDataLen) + // if err != nil { + // return nil, err + // } + // //byte[] zlibData = new byte[zlibDataLen]; + // //rfb.readFully(zlibData); + // readBytes(conn.c, zlibDataLen) + + // // if (rfb.rec != null) { + // // rfb.rec.write(zlibData); + // // } + // // int stream_id = comp_ctl & 0x03; + // // if (tightInflaters[stream_id] == null) { + // // tightInflaters[stream_id] = new Inflater(); + // // } + // // Inflater myInflater = tightInflaters[stream_id]; + // // myInflater.setInput(zlibData); + // // byte[] buf = new byte[dataSize]; + // // myInflater.inflate(buf); + // // if (rfb.rec != null && !rfb.recordFromBeginning) { + // // rfb.recordCompressedData(buf); + // // } + + // // if (numColors != 0) { + // // // Indexed colors. + // // if (numColors == 2) { + // // // Two colors. + // // if (bytesPixel == 1) { + // // decodeMonoData(x, y, w, h, buf, palette8); + // // } else { + // // decodeMonoData(x, y, w, h, buf, palette24); + // // } + // // } else { + // // // More than two colors (assuming bytesPixel == 4). + // // int i = 0; + // // for (int dy = y; dy < y + h; dy++) { + // // for (int dx = x; dx < x + w; dx++) { + // // pixels24[dy * rfb.framebufferWidth + dx] = palette24[buf[i++] & 0xFF]; + // // } + // // } + // // } + // // } else if (useGradient) { + // // // Compressed "Gradient"-filtered data (assuming bytesPixel == 4). + // // decodeGradientData(x, y, w, h, buf); + // // } else { + // // // Compressed truecolor data. + // // if (bytesPixel == 1) { + // // int destOffset = y * rfb.framebufferWidth + x; + // // for (int dy = 0; dy < h; dy++) { + // // System.arraycopy(buf, dy * w, pixels8, destOffset, w); + // // destOffset += rfb.framebufferWidth; + // // } + // // } else { + // // int srcOffset = 0; + // // int destOffset, i; + // // for (int dy = 0; dy < h; dy++) { + // // myInflater.inflate(buf); + // // destOffset = (y + dy) * rfb.framebufferWidth + x; + // // for (i = 0; i < w; i++) { + // // pixels24[destOffset + i] = (buf[srcOffset] & 0xFF) << 16 | (buf[srcOffset + 1] & 0xFF) << 8 + // // | (buf[srcOffset + 2] & 0xFF); + // // srcOffset += 3; + // // } + // // } + // // } + // // } + // } + + return +} + +func readTightData(conn *ClientConn, dataSize int) ([]byte, error) { + if int(dataSize) < TightMinToCompress { + return conn.readBytes(int(dataSize)) + } + zlibDataLen, err := conn.readCompactLen() + fmt.Printf("compactlen=%d\n", zlibDataLen) + if err != nil { + return nil, err + } + //byte[] zlibData = new byte[zlibDataLen]; + //rfb.readFully(zlibData); + return conn.readBytes(zlibDataLen) +} diff --git a/vnc/enc-zlib.go b/vnc/enc-zlib.go new file mode 100644 index 0000000..f28b023 --- /dev/null +++ b/vnc/enc-zlib.go @@ -0,0 +1,22 @@ +package vnc + +import "io" + +type ZLibEncoding struct { + Colors []Color +} + +func (z *ZLibEncoding) Type() int32 { + return 6 +} +func (z *ZLibEncoding) Read(conn *ClientConn, rect *Rectangle, r io.Reader) (Encoding, error) { + //conn := &DataSource{conn: conn.c, PixelFormat: conn.PixelFormat} + //bytesPerPixel := c.PixelFormat.BPP / 8 + len, _ := conn.readUint32() + _, err := conn.readBytes(int(len)) + + if err != nil { + return nil, err + } + return z, nil +} diff --git a/vnc/enc-zrle.go b/vnc/enc-zrle.go new file mode 100644 index 0000000..7731a8a --- /dev/null +++ b/vnc/enc-zrle.go @@ -0,0 +1,22 @@ +package vnc + +import "io" + +type ZRLEEncoding struct { + Colors []Color +} + +func (z *ZRLEEncoding) Type() int32 { + return 16 +} +func (z *ZRLEEncoding) Read(conn *ClientConn, rect *Rectangle, r io.Reader) (Encoding, error) { + //conn := &DataSource{conn: conn.c, PixelFormat: conn.PixelFormat} + //bytesPerPixel := c.PixelFormat.BPP / 8 + len, _ := conn.readUint32() + _, err := conn.readBytes(int(len)) + + if err != nil { + return nil, err + } + return z, nil +} diff --git a/vnc/encoding.go b/vnc/encoding.go new file mode 100644 index 0000000..9c7b09e --- /dev/null +++ b/vnc/encoding.go @@ -0,0 +1,45 @@ +package vnc + +import "io" + +// An Encoding implements a method for encoding pixel data that is +// sent by the server to the client. +type Encoding interface { + // The number that uniquely identifies this encoding type. + Type() int32 + + // Read reads the contents of the encoded pixel data from the reader. + // This should return a new Encoding implementation that contains + // the proper data. + Read(*ClientConn, *Rectangle, io.Reader) (Encoding, error) +} + +const ( + EncodingRaw = 0 + EncodingCopyRect = 1 + EncodingRRE = 2 + EncodingCoRRE = 4 + EncodingHextile = 5 + EncodingZlib = 6 + EncodingTight = 7 + EncodingZRLE = 16 +) + +type CopyRectEncoding struct { + Colors []Color + copyRectSrcX uint16 + copyRectSrcY uint16 +} + +func (z *CopyRectEncoding) Type() int32 { + return 1 +} +func (z *CopyRectEncoding) Read(conn *ClientConn, rect *Rectangle, r io.Reader) (Encoding, error) { + //conn := &DataSource{conn: conn.c, PixelFormat: conn.PixelFormat} + //bytesPerPixel := c.PixelFormat.BPP / 8 + z.copyRectSrcX, _ = conn.readUint16() + z.copyRectSrcY, _ = conn.readUint16() + return z, nil +} + +////////// diff --git a/vnc/pixel_format.go b/vnc/pixel_format.go new file mode 100644 index 0000000..c951298 --- /dev/null +++ b/vnc/pixel_format.go @@ -0,0 +1,135 @@ +package vnc + +import ( + "bytes" + "encoding/binary" + "io" +) + +func readPixelFormat(r io.Reader, result *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 *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 +} diff --git a/vnc/pointer.go b/vnc/pointer.go new file mode 100644 index 0000000..86d84e3 --- /dev/null +++ b/vnc/pointer.go @@ -0,0 +1,16 @@ +package vnc + +// 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 +) diff --git a/vnc/recorder.go b/vnc/recorder.go new file mode 100644 index 0000000..a9aad24 --- /dev/null +++ b/vnc/recorder.go @@ -0,0 +1,37 @@ +package vnc + +import ( + "os" +) + +type Recorder struct { + RBSFileName string + fileHandle *os.File + logger Logger +} + +func NewRecorder(saveFilePath string, logger Logger) *Recorder { + rec := Recorder{RBSFileName: saveFilePath} + var err error + rec.fileHandle, err = os.OpenFile(saveFilePath, os.O_RDWR|os.O_CREATE, 0755) + if err != nil { + logger.Errorf("unable to open file: %s, error: %v", saveFilePath, err) + return nil + } + return &rec +} + +func (r *Recorder) Write(data []byte) error { + _, err := r.fileHandle.Write(data) + return err +} + +// func (r *Recorder) WriteUInt8(data uint8) error { +// buf := make([]byte, 1) +// buf[0] = byte(data) // cast int8 to byte +// return r.Write(buf) +// } + +func (r *Recorder) Close() { + r.fileHandle.Close() +} diff --git a/vnc/rectangle.go b/vnc/rectangle.go new file mode 100644 index 0000000..3211635 --- /dev/null +++ b/vnc/rectangle.go @@ -0,0 +1,26 @@ +package vnc + +// Rectangle represents a rectangle of pixel data. +type Rectangle struct { + X uint16 + Y uint16 + Width uint16 + Height uint16 + Enc Encoding +} + +// PixelFormat describes the way a pixel is formatted for a VNC connection. +// +// See RFC 6143 Section 7.4 for information on each of the fields. +type PixelFormat struct { + BPP uint8 + Depth uint8 + BigEndian bool + TrueColor bool + RedMax uint16 + GreenMax uint16 + BlueMax uint16 + RedShift uint8 + GreenShift uint8 + BlueShift uint8 +} diff --git a/vnc/server_messages.go b/vnc/server_messages.go new file mode 100644 index 0000000..8f6de43 --- /dev/null +++ b/vnc/server_messages.go @@ -0,0 +1,205 @@ +package vnc + +import ( + "encoding/binary" + "encoding/json" + "fmt" + "io" +) + + +// FramebufferUpdateMessage consists of a sequence of rectangles of +// pixel data that the client should put into its framebuffer. +type FramebufferUpdateMessage struct { + Rectangles []Rectangle +} + + +func (r *Rectangle) String() string { + return fmt.Sprintf("(%d,%d) (width: %d, height: %d), Enc= %d", r.X, r.Y, r.Width, r.Height, r.Enc.Type()) +} + +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 (*FramebufferUpdateMessage) Read(c *ClientConn, r io.Reader) (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]Encoding) + for _, enc := range c.Encs { + encMap[enc.Type()] = enc + } + + // We must always support the raw encoding + rawEnc := new(RawEncoding) + encMap[rawEnc.Type()] = rawEnc + fmt.Printf("numrects= %d\n", numRects) + + rects := make([]Rectangle, numRects) + for i := uint16(0); i < numRects; i++ { + fmt.Printf("###############rect################: %d\n", i) + var encodingType int32 + + 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, 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 []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 *ClientConn, r io.Reader) (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([]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 + } + } + + // Update the connection's color map + c.ColorMap[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(*ClientConn, io.Reader) (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(c *ClientConn, r io.Reader) (ServerMessage, error) { + // Read off the padding + var padding [1]byte + if _, err := io.ReadFull(r, padding[:]); 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 +}