mirror of
https://github.com/openstf/stf
synced 2025-10-06 03:50:04 +02:00
VNC authentication works, although there is no UI for adding passwords
yet. Direct database manipulation is required for now.
This commit is contained in:
parent
a902c66131
commit
5b5520b705
8 changed files with 332 additions and 32 deletions
|
@ -8,6 +8,9 @@ var uuid = require('node-uuid')
|
|||
var jpeg = require('jpeg-turbo')
|
||||
|
||||
var logger = require('../../../../util/logger')
|
||||
var grouputil = require('../../../../util/grouputil')
|
||||
var wire = require('../../../../wire')
|
||||
var wireutil = require('../../../../wire/util')
|
||||
var lifecycle = require('../../../../util/lifecycle')
|
||||
|
||||
var VncServer = require('./util/server')
|
||||
|
@ -15,11 +18,75 @@ var VncConnection = require('./util/connection')
|
|||
var PointerTranslator = require('./util/pointertranslator')
|
||||
|
||||
module.exports = syrup.serial()
|
||||
.dependency(require('../../support/router'))
|
||||
.dependency(require('../../support/push'))
|
||||
.dependency(require('../screen/stream'))
|
||||
.dependency(require('../touch'))
|
||||
.define(function(options, screenStream, touch) {
|
||||
.dependency(require('../group'))
|
||||
.dependency(require('../solo'))
|
||||
.define(function(options, router, push, screenStream, touch, group, solo) {
|
||||
var log = logger.createLogger('device:plugins:vnc')
|
||||
|
||||
function vncAuthHandler(data) {
|
||||
log.info('VNC authentication attempt using "%s"', data.fingerprint)
|
||||
|
||||
var resolver = Promise.defer()
|
||||
|
||||
function notify() {
|
||||
group.get()
|
||||
.then(function(currentGroup) {
|
||||
push.send([
|
||||
solo.channel
|
||||
, wireutil.envelope(new wire.JoinGroupByVncAuthResponseMessage(
|
||||
options.serial
|
||||
, data.response.toString('hex')
|
||||
, currentGroup.group
|
||||
))
|
||||
])
|
||||
})
|
||||
.catch(grouputil.NoGroupError, function() {
|
||||
push.send([
|
||||
solo.channel
|
||||
, wireutil.envelope(new wire.JoinGroupByVncAuthResponseMessage(
|
||||
options.serial
|
||||
, data.response.toString('hex')
|
||||
))
|
||||
])
|
||||
})
|
||||
}
|
||||
|
||||
function joinListener(newGroup, identifier) {
|
||||
if (!data.response.equals(new Buffer(identifier || '', 'hex'))) {
|
||||
resolver.reject(new Error('Someone else took the device'))
|
||||
}
|
||||
}
|
||||
|
||||
function autojoinListener(identifier, joined) {
|
||||
if (data.response.equals(new Buffer(identifier, 'hex'))) {
|
||||
if (joined) {
|
||||
resolver.resolve()
|
||||
}
|
||||
else {
|
||||
resolver.reject(new Error('Device is already in use'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
group.on('join', joinListener)
|
||||
group.on('autojoin', autojoinListener)
|
||||
router.on(wire.VncAuthResponsesUpdatedMessage, notify)
|
||||
|
||||
notify()
|
||||
|
||||
return resolver.promise
|
||||
.timeout(5000)
|
||||
.finally(function() {
|
||||
group.removeListener('join', joinListener)
|
||||
group.removeListener('autojoin', autojoinListener)
|
||||
router.removeListener(wire.VncAuthResponsesUpdatedMessage, notify)
|
||||
})
|
||||
}
|
||||
|
||||
function createServer() {
|
||||
log.info('Starting VNC server on port %d', options.vncPort)
|
||||
|
||||
|
@ -27,6 +94,11 @@ module.exports = syrup.serial()
|
|||
name: options.serial
|
||||
, width: options.vncInitialSize[0]
|
||||
, height: options.vncInitialSize[1]
|
||||
, security: [{
|
||||
type: VncConnection.SECURITY_VNC
|
||||
, challenge: new Buffer(16).fill(0)
|
||||
, auth: vncAuthHandler
|
||||
}]
|
||||
}
|
||||
|
||||
var vnc = new VncServer(net.createServer({
|
||||
|
@ -57,6 +129,8 @@ module.exports = syrup.serial()
|
|||
return createServer()
|
||||
.then(function(vnc) {
|
||||
vnc.on('connection', function(conn) {
|
||||
log.info('New VNC connection from %s', conn.conn.remoteAddress)
|
||||
|
||||
var id = util.format('vnc-%s', uuid.v4())
|
||||
|
||||
var connState = {
|
||||
|
@ -89,23 +163,6 @@ module.exports = syrup.serial()
|
|||
touch.touchCommit()
|
||||
})
|
||||
|
||||
function vncStartListener(frameProducer) {
|
||||
return new Promise(function(resolve/*, reject*/) {
|
||||
connState.frameWidth = frameProducer.banner.virtualWidth
|
||||
connState.frameHeight = frameProducer.banner.virtualHeight
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
|
||||
function vncFrameListener(frame) {
|
||||
return new Promise(function(resolve/*, reject*/) {
|
||||
connState.lastFrame = frame
|
||||
connState.lastFrameTime = Date.now()
|
||||
maybeSendFrame()
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
|
||||
function maybeSendFrame() {
|
||||
if (!connState.updateRequests) {
|
||||
return
|
||||
|
@ -142,6 +199,27 @@ module.exports = syrup.serial()
|
|||
connState.sentFrameTime = connState.lastFrameTime
|
||||
}
|
||||
|
||||
function vncStartListener(frameProducer) {
|
||||
return new Promise(function(resolve/*, reject*/) {
|
||||
connState.frameWidth = frameProducer.banner.virtualWidth
|
||||
connState.frameHeight = frameProducer.banner.virtualHeight
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
|
||||
function vncFrameListener(frame) {
|
||||
return new Promise(function(resolve/*, reject*/) {
|
||||
connState.lastFrame = frame
|
||||
connState.lastFrameTime = Date.now()
|
||||
maybeSendFrame()
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
|
||||
function groupLeaveListener() {
|
||||
conn.end()
|
||||
}
|
||||
|
||||
conn.on('authenticated', function() {
|
||||
screenStream.updateProjection(
|
||||
options.vncInitialSize[0], options.vncInitialSize[1])
|
||||
|
@ -157,7 +235,7 @@ module.exports = syrup.serial()
|
|||
})
|
||||
|
||||
conn.on('formatchange', function(format) {
|
||||
var same = os.endianness() == 'BE' == format.bigEndianFlag
|
||||
var same = os.endianness() === 'BE' === format.bigEndianFlag
|
||||
switch (format.bitsPerPixel) {
|
||||
case 8:
|
||||
connState.frameConfig = {
|
||||
|
@ -191,7 +269,14 @@ module.exports = syrup.serial()
|
|||
|
||||
conn.on('close', function() {
|
||||
screenStream.broadcastSet.remove(id)
|
||||
group.removeListener('leave', groupLeaveListener)
|
||||
})
|
||||
|
||||
conn.on('userActivity', function() {
|
||||
group.keepalive()
|
||||
})
|
||||
|
||||
group.on('leave', groupLeaveListener)
|
||||
})
|
||||
|
||||
lifecycle.observe(function() {
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
var util = require('util')
|
||||
var os = require('os')
|
||||
var crypto = require('crypto')
|
||||
|
||||
var EventEmitter = require('eventemitter3').EventEmitter
|
||||
var debug = require('debug')('vnc:connection')
|
||||
var Promise = require('bluebird')
|
||||
|
||||
var PixelFormat = require('./pixelformat')
|
||||
var vncauth = require('../../../../../util/vncauth')
|
||||
|
||||
function VncConnection(conn, options) {
|
||||
this.options = options
|
||||
|
@ -21,7 +24,15 @@ function VncConnection(conn, options) {
|
|||
this._changeState(VncConnection.STATE_NEED_CLIENT_VERSION)
|
||||
|
||||
this._serverVersion = VncConnection.V3_008
|
||||
this._serverSupportedSecurity = [VncConnection.SECURITY_NONE]
|
||||
this._serverSupportedSecurity = this.options.security
|
||||
this._serverSupportedSecurityByType =
|
||||
this.options.security.reduce(
|
||||
function(map, method) {
|
||||
map[method.type] = method
|
||||
return map
|
||||
}
|
||||
, Object.create(null)
|
||||
)
|
||||
this._serverWidth = this.options.width
|
||||
this._serverHeight = this.options.height
|
||||
this._serverPixelFormat = new PixelFormat({
|
||||
|
@ -45,12 +56,16 @@ function VncConnection(conn, options) {
|
|||
this._clientEncodings = []
|
||||
this._clientCutTextLength = 0
|
||||
|
||||
this._authChallenge = this.options.challenge || crypto.randomBytes(16)
|
||||
|
||||
this.conn = conn
|
||||
.on('error', this._bound._errorListener)
|
||||
.on('readable', this._bound._readableListener)
|
||||
.on('end', this._bound._endListener)
|
||||
.on('close', this._bound._closeListener)
|
||||
|
||||
this._blockingOps = []
|
||||
|
||||
this._writeServerVersion()
|
||||
this._read()
|
||||
}
|
||||
|
@ -80,6 +95,7 @@ var StateReverse = Object.create(null), State = {
|
|||
STATE_NEED_CLIENT_VERSION: 10
|
||||
, STATE_NEED_CLIENT_SECURITY: 20
|
||||
, STATE_NEED_CLIENT_INIT: 30
|
||||
, STATE_NEED_CLIENT_VNC_AUTH: 31
|
||||
, STATE_NEED_CLIENT_MESSAGE: 40
|
||||
, STATE_NEED_CLIENT_MESSAGE_SETPIXELFORMAT: 50
|
||||
, STATE_NEED_CLIENT_MESSAGE_SETENCODINGS: 60
|
||||
|
@ -171,18 +187,12 @@ VncConnection.prototype._writeSupportedSecurity = function() {
|
|||
|
||||
chunk[0] = this._serverSupportedSecurity.length
|
||||
this._serverSupportedSecurity.forEach(function(security, i) {
|
||||
chunk[1 + i] = security
|
||||
chunk[1 + i] = security.type
|
||||
})
|
||||
|
||||
this._write(chunk)
|
||||
}
|
||||
|
||||
VncConnection.prototype._writeSelectedSecurity = function() {
|
||||
var chunk = new Buffer(4)
|
||||
chunk.writeUInt32BE(VncConnection.SECURITY_NONE, 0)
|
||||
this._write(chunk)
|
||||
}
|
||||
|
||||
VncConnection.prototype._writeSecurityResult = function(result, reason) {
|
||||
var chunk
|
||||
switch (result) {
|
||||
|
@ -224,11 +234,40 @@ VncConnection.prototype._writeServerInit = function() {
|
|||
this._write(chunk)
|
||||
}
|
||||
|
||||
VncConnection.prototype._writeVncAuthChallenge = function() {
|
||||
var vncSec = this._serverSupportedSecurityByType[VncConnection.SECURITY_VNC]
|
||||
debug('vnc auth challenge', vncSec.challenge)
|
||||
this._write(vncSec.challenge)
|
||||
}
|
||||
|
||||
VncConnection.prototype._readableListener = function() {
|
||||
this._read()
|
||||
}
|
||||
|
||||
VncConnection.prototype._read = function() {
|
||||
Promise.all(this._blockingOps).bind(this)
|
||||
.then(this._unguardedRead)
|
||||
}
|
||||
|
||||
VncConnection.prototype._auth = function(type, data) {
|
||||
var security = this._serverSupportedSecurityByType[type]
|
||||
this._blockingOps.push(
|
||||
security.auth(data).bind(this)
|
||||
.then(function() {
|
||||
this._changeState(VncConnection.STATE_NEED_CLIENT_INIT)
|
||||
this._writeSecurityResult(VncConnection.SECURITYRESULT_OK)
|
||||
this.emit('authenticated')
|
||||
this._read()
|
||||
})
|
||||
.catch(function() {
|
||||
this._writeSecurityResult(
|
||||
VncConnection.SECURITYRESULT_FAIL, 'Authentication failure')
|
||||
this.end()
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
VncConnection.prototype._unguardedRead = function() {
|
||||
var chunk, lo, hi
|
||||
while (this._append(this.conn.read())) {
|
||||
do {
|
||||
|
@ -250,14 +289,35 @@ VncConnection.prototype._read = function() {
|
|||
if ((chunk = this._consume(1))) {
|
||||
if ((this._clientSecurity = this._parseSecurity(chunk)) === null) {
|
||||
this._writeSecurityResult(
|
||||
VncConnection.SECURITYRESULT_FAIL, 'Unsupported security type')
|
||||
VncConnection.SECURITYRESULT_FAIL, 'Unimplemented security type')
|
||||
this.end()
|
||||
return
|
||||
}
|
||||
debug('client security', this._clientSecurity)
|
||||
this._writeSecurityResult(VncConnection.SECURITYRESULT_OK)
|
||||
this.emit('authenticated')
|
||||
this._changeState(VncConnection.STATE_NEED_CLIENT_INIT)
|
||||
if (!(this._clientSecurity in this._serverSupportedSecurityByType)) {
|
||||
this._writeSecurityResult(
|
||||
VncConnection.SECURITYRESULT_FAIL, 'Unsupported security type')
|
||||
this.end()
|
||||
return
|
||||
}
|
||||
switch (this._clientSecurity) {
|
||||
case VncConnection.SECURITY_NONE:
|
||||
this._auth(VncConnection.SECURITY_NONE)
|
||||
return
|
||||
case VncConnection.SECURITY_VNC:
|
||||
this._writeVncAuthChallenge()
|
||||
this._changeState(VncConnection.STATE_NEED_CLIENT_VNC_AUTH)
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
case VncConnection.STATE_NEED_CLIENT_VNC_AUTH:
|
||||
if ((chunk = this._consume(16))) {
|
||||
this._auth(VncConnection.SECURITY_VNC, {
|
||||
response: chunk
|
||||
, fingerprint: vncauth.format(chunk)
|
||||
})
|
||||
return
|
||||
}
|
||||
break
|
||||
case VncConnection.STATE_NEED_CLIENT_INIT:
|
||||
|
@ -284,14 +344,17 @@ VncConnection.prototype._read = function() {
|
|||
VncConnection.STATE_NEED_CLIENT_MESSAGE_FBUPDATEREQUEST)
|
||||
break
|
||||
case VncConnection.CLIENT_MESSAGE_KEYEVENT:
|
||||
this.emit('userActivity')
|
||||
this._changeState(
|
||||
VncConnection.STATE_NEED_CLIENT_MESSAGE_KEYEVENT)
|
||||
break
|
||||
case VncConnection.CLIENT_MESSAGE_POINTEREVENT:
|
||||
this.emit('userActivity')
|
||||
this._changeState(
|
||||
VncConnection.STATE_NEED_CLIENT_MESSAGE_POINTEREVENT)
|
||||
break
|
||||
case VncConnection.CLIENT_MESSAGE_CLIENTCUTTEXT:
|
||||
this.emit('userActivity')
|
||||
this._changeState(
|
||||
VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT)
|
||||
break
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue