119 lines
4.1 KiB
Go
119 lines
4.1 KiB
Go
package services
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/binary"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// NewVNCHandler implements a minimal RFB handshake and then fails auth to keep interaction realistic.
|
|
func NewVNCHandler(log LoggerFunc) Handler {
|
|
return func(conn net.Conn) {
|
|
remote := conn.RemoteAddr().String()
|
|
conn.SetDeadline(time.Now().Add(20 * time.Second))
|
|
|
|
// Send server protocol version banner
|
|
serverVer := "RFB 003.008\n"
|
|
_, _ = conn.Write([]byte(serverVer))
|
|
|
|
r := bufio.NewReader(conn)
|
|
clientVer, _ := r.ReadString('\n')
|
|
clientVer = strings.TrimSpace(clientVer)
|
|
if clientVer == "" {
|
|
return
|
|
}
|
|
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "vnc", Details: map[string]string{"event":"version","client":clientVer}})
|
|
|
|
// Offer two security types: None (1) and VNC Authentication (2)
|
|
_, _ = conn.Write([]byte{2, 1, 2})
|
|
|
|
// Read client's selected security type (1 byte)
|
|
b, _ := r.ReadByte()
|
|
// If client chose None (1), send SecurityResult OK and minimal ServerInit
|
|
if b == 1 {
|
|
// OK result
|
|
_, _ = conn.Write([]byte{0, 0, 0, 0})
|
|
// ClientInit (read but ignore)
|
|
_, _ = r.ReadByte()
|
|
// Send ServerInit: framebuffer width/height, pixel format, name length + name
|
|
// Width=800, Height=600
|
|
srv := make([]byte, 0, 24)
|
|
srv = append(srv, 0x03, 0x20) // width 800
|
|
srv = append(srv, 0x02, 0x58) // height 600
|
|
// Pixel format (16 bytes): 32bpp, 24 depth, big endian=0, trueColor=1, max red/green/blue, shifts
|
|
srv = append(srv, 32, 24, 0, 1, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 16, 8, 0)
|
|
// pad 3 bytes
|
|
srv = append(srv, 0, 0, 0)
|
|
// Name length and name
|
|
name := []byte("Ubuntu VNC")
|
|
nameLen := []byte{0, 0, byte(len(name) >> 8), byte(len(name) & 0xff)}
|
|
// Actually RFB uses 32-bit name length big-endian
|
|
nameLen = []byte{0, 0, 0, byte(len(name))}
|
|
srv = append(srv, nameLen...)
|
|
srv = append(srv, name...)
|
|
_, _ = conn.Write(srv)
|
|
// Enter a simple loop to keep client engaged: respond to FramebufferUpdateRequest (type=3)
|
|
for {
|
|
// Read message type
|
|
mt, err := r.ReadByte()
|
|
if err != nil { return }
|
|
switch mt {
|
|
case 0: // SetPixelFormat: skip 3 pad + 16 bytes
|
|
skip := make([]byte, 19)
|
|
if _, err := r.Read(skip); err != nil { return }
|
|
case 2: // SetEncodings: 1 pad + int16 num + list
|
|
pad, _ := r.ReadByte(); _ = pad
|
|
Hdr := make([]byte, 2)
|
|
if _, err := r.Read(Hdr); err != nil { return }
|
|
nEnc := int(binary.BigEndian.Uint16(Hdr))
|
|
encs := make([]byte, nEnc*4)
|
|
if _, err := r.Read(encs); err != nil { return }
|
|
case 3: // FramebufferUpdateRequest
|
|
req := make([]byte, 9)
|
|
if _, err := r.Read(req); err != nil { return }
|
|
// incremental := req[0]
|
|
// x,y,w,h (big-endian)
|
|
// Respond with one small RAW rectangle 64x32 at 0,0
|
|
reply := make([]byte, 0, 4+12+64*32*4)
|
|
// message-type=0 (FramebufferUpdate) + pad
|
|
reply = append(reply, 0, 0)
|
|
// number-of-rectangles = 1
|
|
reply = append(reply, 0, 1)
|
|
// x=0,y=0,w=64,h=32
|
|
rb := make([]byte, 12)
|
|
// x,y
|
|
binary.BigEndian.PutUint16(rb[0:2], 0)
|
|
binary.BigEndian.PutUint16(rb[2:4], 0)
|
|
// w,h
|
|
binary.BigEndian.PutUint16(rb[4:6], 64)
|
|
binary.BigEndian.PutUint16(rb[6:8], 32)
|
|
// encoding RAW = 0
|
|
binary.BigEndian.PutUint32(rb[8:12], 0)
|
|
reply = append(reply, rb...)
|
|
// pixel data (32bpp). Simple grey fill (e.g., 0x202020ff little-endian if needed)
|
|
px := make([]byte, 64*32*4)
|
|
for i := 0; i < len(px); i += 4 {
|
|
// BGRA for many viewers when little endian flag=0; we keep it neutral
|
|
px[i+0] = 0x20
|
|
px[i+1] = 0x20
|
|
px[i+2] = 0x20
|
|
px[i+3] = 0xFF
|
|
}
|
|
reply = append(reply, px...)
|
|
if _, err := conn.Write(reply); err != nil { return }
|
|
default:
|
|
// Unknown or unhandled; try to keep reading, but bail on error
|
|
}
|
|
}
|
|
return
|
|
}
|
|
// Otherwise (e.g., VNC Auth), send failure
|
|
_, _ = conn.Write([]byte{0, 0, 0, 1})
|
|
reason := []byte("Authentication failed")
|
|
msg := append([]byte{0, 0, 0, byte(len(reason))}, reason...)
|
|
_, _ = conn.Write(msg)
|
|
}
|
|
}
|