From a0f07406cca387a771e7d796b51938962d4cc993 Mon Sep 17 00:00:00 2001 From: Moe Date: Sat, 6 Jun 2020 18:55:01 -0700 Subject: [PATCH] Move Probe and ONVIF functions to separate lib (scanners.js) --- camera.js | 2 + libs/scanners.js | 265 +++++++++++++++++++++++++++++++++++++++++ libs/socketio.js | 130 -------------------- libs/webServerPaths.js | 165 ------------------------- 4 files changed, 267 insertions(+), 295 deletions(-) create mode 100644 libs/scanners.js diff --git a/camera.js b/camera.js index 5cbb6655..2e142ccc 100644 --- a/camera.js +++ b/camera.js @@ -81,6 +81,8 @@ require('./libs/ffmpeg.js')(s,config,lang,function(ffmpeg){ require('./libs/customAutoLoad.js')(s,config,lang,app,io) //scheduling engine require('./libs/shinobiHub.js')(s,config,lang,app,io) + //onvif, ffprobe engine + require('./libs/scanners.js')(s,config,lang,app,io) //scheduling engine require('./libs/scheduler.js')(s,config,lang,app,io) //on-start actions, daemon(s) starter diff --git a/libs/scanners.js b/libs/scanners.js new file mode 100644 index 00000000..c85c12a6 --- /dev/null +++ b/libs/scanners.js @@ -0,0 +1,265 @@ +var os = require('os'); +var exec = require('child_process').exec; +var onvif = require("node-onvif"); +module.exports = function(s,config,lang,app,io){ + const activeProbes = {} + const runFFprobe = (url,auth,callback) => { + var endData = {ok: false} + if(!url){ + endData.error = 'Missing URL' + callback(endData) + return + } + if(activeProbes[auth]){ + endData.error = 'Account is already probing' + callback(endData) + return + } + activeProbes[auth] = 1 + const probeCommand = s.splitForFFPMEG(`-v quiet -print_format json -show_format -show_streams -i "${url}"`).join(' ') + exec('ffprobe ' + probeCommand,function(err,stdout,stderr){ + delete(activeProbes[auth]) + if(err){ + endData.error = (err) + }else{ + endData.ok = true + endData.result = s.parseJSON(stdout) + } + endData.probe = probeCommand + callback(endData) + }) + } + const runOnvifScanner = (options,foundCameraCallback) => { + var ip = options.ip.replace(/ /g,'') + var ports = options.port.replace(/ /g,'') + if(options.ip === ''){ + var interfaces = os.networkInterfaces() + var addresses = [] + for (var k in interfaces) { + for (var k2 in interfaces[k]) { + var address = interfaces[k][k2] + if (address.family === 'IPv4' && !address.internal) { + addresses.push(address.address) + } + } + } + const addressRange = [] + addresses.forEach(function(address){ + if(address.indexOf('0.0.0')>-1){return false} + var addressPrefix = address.split('.') + delete(addressPrefix[3]); + addressPrefix = addressPrefix.join('.') + addressRange.push(`${addressPrefix}1-${addressPrefix}254`) + }) + ip = addressRange.join(',') + } + if(ports === ''){ + ports = '80,8080,8000,7575,8081,554' + } + if(ports.indexOf('-') > -1){ + ports = ports.split('-') + var portRangeStart = ports[0] + var portRangeEnd = ports[1] + ports = s.portRange(portRangeStart,portRangeEnd); + }else{ + ports = ports.split(',') + } + var ipList = options.ipList + var onvifUsername = options.user || '' + var onvifPassword = options.pass || '' + ip.split(',').forEach(function(addressRange){ + var ipRangeStart = addressRange[0] + var ipRangeEnd = addressRange[1] + if(addressRange.indexOf('-')>-1){ + addressRange = addressRange.split('-'); + ipRangeStart = addressRange[0] + ipRangeEnd = addressRange[1] + }else{ + ipRangeStart = addressRange + ipRangeEnd = addressRange + } + if(!ipList){ + ipList = s.ipRange(ipRangeStart,ipRangeEnd); + }else{ + ipList = ipList.concat(s.ipRange(ipRangeStart,ipRangeEnd)) + } + }) + var hitList = [] + ipList.forEach((ipEntry,n) => { + ports.forEach((portEntry,nn) => { + hitList.push({ + xaddr : 'http://' + ipEntry + ':' + portEntry + '/onvif/device_service', + user : onvifUsername, + pass : onvifPassword, + ip: ipEntry, + port: portEntry, + }) + }) + }) + var responseList = [] + hitList.forEach(async (camera) => { + try{ + var device = new onvif.OnvifDevice(camera) + var info = await device.init() + var date = await device.services.device.getSystemDateAndTime() + var stream = await device.services.media.getStreamUri({ + ProfileToken : device.current_profile.token, + Protocol : 'RTSP' + }) + var cameraResponse = { + ip: camera.ip, + port: camera.port, + info: info, + date: date, + uri: stream.data.GetStreamUriResponse.MediaUri.Uri + } + responseList.push(cameraResponse) + if(foundCameraCallback)foundCameraCallback(Object.assign(cameraResponse,{f: 'onvif'})) + }catch(err){ + console.log(err) + s.debugLog(err) + } + }) + return responseList + } + const runOnvifMethod = (onvifOptions,callback) => { + var response = {ok: false} + console.log(onvifOptions) + var errorMessage = function(msg,error){ + response.ok = false + response.msg = msg + response.error = error + callback(response) + } + var actionCallback = function(onvifActionResponse){ + response.ok = true + if(onvifActionResponse.data){ + response.responseFromDevice = onvifActionResponse.data + }else{ + response.responseFromDevice = onvifActionResponse + } + if(onvifActionResponse.soap)response.soap = onvifActionResponse.soap + callback(response) + } + var isEmpty = function(obj) { + for(var key in obj) { + if(obj.hasOwnProperty(key)) + return false; + } + return true; + } + var doAction = function(Camera){ + var completeAction = function(command){ + if(command && command.then){ + command.then(actionCallback).catch(function(error){ + errorMessage('Device Action responded with an error',error) + }) + }else if(command){ + response.ok = true + response.repsonseFromDevice = command + callback(response) + }else{ + response.error = 'Big Errors, Please report it to Shinobi Development' + callback(response) + } + } + var action + if(onvifOptions.auth.service){ + if(Camera.services[onvifOptions.auth.service] === undefined){ + return errorMessage('This is not an available service. Please use one of the following : '+Object.keys(Camera.services).join(', ')) + } + if(Camera.services[onvifOptions.auth.service] === null){ + return errorMessage('This service is not activated. Maybe you are not connected through ONVIF. You can test by attempting to use the "Control" feature with ONVIF in Shinobi.') + } + action = Camera.services[onvifOptions.auth.service][onvifOptions.auth.action] + }else{ + action = Camera[onvifOptions.auth.action] + } + if(!action || typeof action !== 'function'){ + errorMessage(onvifOptions.auth.action+' is not an available ONVIF function. See https://github.com/futomi/node-onvif for functions.') + }else{ + var argNames = s.getFunctionParamNames(action) + var options + var command + if(argNames[0] === 'options' || argNames[0] === 'params'){ + options = onvifOptions.options || {} + } + if(onvifOptions.auth.service){ + command = Camera.services[onvifOptions.auth.service][onvifOptions.auth.action](options) + }else{ + command = Camera[onvifOptions.auth.action](options) + } + console.log(Camera) + completeAction(command) + } + } + if(!s.group[onvifOptions.auth.ke].activeMonitors[onvifOptions.auth.id].onvifConnection){ + //prepeare onvif connection + var controlURL + var monitorConfig = s.group[onvifOptions.auth.ke].rawMonitorConfigurations[onvifOptions.auth.id] + if(!monitorConfig.details.control_base_url||monitorConfig.details.control_base_url===''){ + controlURL = s.buildMonitorUrl(monitorConfig, true) + }else{ + controlURL = monitorConfig.details.control_base_url + } + var controlURLOptions = s.cameraControlOptionsFromUrl(controlURL,monitorConfig) + //create onvif connection + s.group[onvifOptions.auth.ke].activeMonitors[onvifOptions.auth.id].onvifConnection = new onvif.OnvifDevice({ + xaddr : 'http://' + controlURLOptions.host + ':' + controlURLOptions.port + '/onvif/device_service', + user : controlURLOptions.username, + pass : controlURLOptions.password + }) + var device = s.group[onvifOptions.auth.ke].activeMonitors[onvifOptions.auth.id].onvifConnection + device.init().then((info) => { + if(info)doAction(device) + }).catch(function(error){ + return errorMessage('Device responded with an error',error) + }) + }else{ + doAction(s.group[onvifOptions.auth.ke].activeMonitors[onvifOptions.auth.id].onvifConnection) + } + } + const onWebSocketConnection = async (cn) => { + const tx = function(z){if(!z.ke){z.ke=cn.ke;};cn.emit('f',z);} + cn.on('f',(d) => { + switch(d.f){ + case'onvif': + runOnvifScanner(d,tx) + break; + } + }) + } + s.onWebSocketConnection(onWebSocketConnection) + /** + * API : ONVIF Method Controller + */ + app.all([ + config.webPaths.apiPrefix+':auth/onvif/:ke/:id/:action', + config.webPaths.apiPrefix+':auth/onvif/:ke/:id/:service/:action' + ],function (req,res){ + s.auth(req.params,function(user){ + runOnvifMethod({ + auth: { + ke: req.params.ke, + id: req.params.id, + auth: req.params.auth, + action: req.params.action, + service: req.params.service, + }, + options: s.getPostData(req,'options',true) || s.getPostData(req,'params',true), + },(endData) => { + s.closeJsonResponse(res,endData) + }) + },res,req); + }) + /** + * API : FFprobe + */ + app.get(config.webPaths.apiPrefix+':auth/probe/:ke',function (req,res){ + s.auth(req.params,function(user){ + runFFprobe(req.query.url,req.params.auth,(endData) => { + s.closeJsonResponse(res,endData) + }) + },res,req); + }) +} diff --git a/libs/socketio.js b/libs/socketio.js index 717a41d3..c65987b9 100644 --- a/libs/socketio.js +++ b/libs/socketio.js @@ -1,10 +1,8 @@ -var os = require('os'); var moment = require('moment'); var execSync = require('child_process').execSync; var exec = require('child_process').exec; var spawn = require('child_process').spawn; var jsonfile = require("jsonfile"); -var onvif = require("node-onvif"); module.exports = function(s,config,lang,io){ s.clientSocketConnection = {} //send data to socket client function @@ -787,134 +785,6 @@ module.exports = function(s,config,lang,io){ break; } break; - // case'video': - // switch(d.ff){ - // case'fix': - // s.video('fix',d) - // break; - // } - // break; - case'ffprobe': - if(s.group[cn.ke].users[cn.auth]){ - switch(d.ff){ - case'stop': - exec('kill -9 '+s.group[cn.ke].users[cn.auth].ffprobe.pid,{detatched: true}) - break; - default: - if(s.group[cn.ke].users[cn.auth].ffprobe){ - return - } - s.group[cn.ke].users[cn.auth].ffprobe=1; - tx({f:'ffprobe_start'}) - exec('ffprobe '+('-v quiet -print_format json -show_format -show_streams '+d.query),function(err,data){ - tx({f:'ffprobe_data',data:data.toString('utf8')}) - delete(s.group[cn.ke].users[cn.auth].ffprobe) - tx({f:'ffprobe_stop'}) - }) - //auto kill in 30 seconds - setTimeout(function(){ - exec('kill -9 '+d.pid,{detached: true}) - },30000) - break; - } - } - break; - case'onvif': - d.ip=d.ip.replace(/ /g,''); - d.port=d.port.replace(/ /g,''); - if(d.ip===''){ - var interfaces = os.networkInterfaces(); - var addresses = []; - for (var k in interfaces) { - for (var k2 in interfaces[k]) { - var address = interfaces[k][k2]; - if (address.family === 'IPv4' && !address.internal) { - addresses.push(address.address); - } - } - } - d.arr=[] - addresses.forEach(function(v){ - if(v.indexOf('0.0.0')>-1){return false} - v=v.split('.'); - delete(v[3]); - v=v.join('.'); - d.arr.push(v+'1-'+v+'254') - }) - d.ip=d.arr.join(',') - } - if(d.port===''){ - d.port='80,8080,8000,7575,8081,554' - } - d.ip.split(',').forEach(function(v){ - if(v.indexOf('-')>-1){ - v=v.split('-'); - d.IP_RANGE_START = v[0], - d.IP_RANGE_END = v[1]; - }else{ - d.IP_RANGE_START = v; - d.IP_RANGE_END = v; - } - if(!d.IP_LIST){ - d.IP_LIST = s.ipRange(d.IP_RANGE_START,d.IP_RANGE_END); - }else{ - d.IP_LIST=d.IP_LIST.concat(s.ipRange(d.IP_RANGE_START,d.IP_RANGE_END)) - } - //check port - if(d.port.indexOf('-')>-1){ - d.port=d.port.split('-'); - d.PORT_RANGE_START = d.port[0]; - d.PORT_RANGE_END = d.port[1]; - d.PORT_LIST = s.portRange(d.PORT_RANGE_START,d.PORT_RANGE_END); - }else{ - d.PORT_LIST=d.port.split(',') - } - //check user name and pass - d.USERNAME=''; - if(d.user){ - d.USERNAME = d.user - } - d.PASSWORD=''; - if(d.pass){ - d.PASSWORD = d.pass - } - }) - d.cams=[] - d.IP_LIST.forEach(function(ip_entry,n) { - d.PORT_LIST.forEach(function(port_entry,nn) { - var device = new onvif.OnvifDevice({ - xaddr : 'http://' + ip_entry + ':' + port_entry + '/onvif/device_service', - user : d.USERNAME, - pass : d.PASSWORD - }) - device.init().then((info) => { - var data = { - f : 'onvif', - ip : ip_entry, - port : port_entry, - info : info - } - device.services.device.getSystemDateAndTime().then((date) => { - data.date = date - device.services.media.getStreamUri({ - ProfileToken : device.current_profile.token, - Protocol : 'RTSP' - }).then((stream) => { - data.uri = stream.data.GetStreamUriResponse.MediaUri.Uri - tx(data) - }).catch((error) => { - // console.log(error) - }); - }).catch((error) => { - // console.log(error) - }); - }).catch(function(error){ - // console.log(error) - }) - }); - }); - // tx({f:'onvif_end'}) - break; } }catch(er){ s.systemLog('ERROR CATCH 1',er) diff --git a/libs/webServerPaths.js b/libs/webServerPaths.js index 3425eb22..cbd5ced2 100644 --- a/libs/webServerPaths.js +++ b/libs/webServerPaths.js @@ -8,7 +8,6 @@ var execSync = require('child_process').execSync; var exec = require('child_process').exec; var spawn = require('child_process').spawn; var httpProxy = require('http-proxy'); -var onvif = require('node-onvif'); var proxy = httpProxy.createProxyServer({}) var ejs = require('ejs'); var fileupload = require("express-fileupload"); @@ -1911,170 +1910,6 @@ module.exports = function(s,config,lang,app,io){ } }) /** - * API : FFprobe - */ - var activeProbes = {} - app.get(config.webPaths.apiPrefix+':auth/probe/:ke',function (req,res){ - var endData = {ok: false} - s.auth(req.params,function(user){ - var url = req.query.url - if(!url){ - endData.error = 'Missing URL' - s.closeJsonResponse(res,endData) - return - } - if(activeProbes[req.params.auth]){ - endData.error = 'Account is already probing' - s.closeJsonResponse(res,endData) - return - } - activeProbes[req.params.auth] = 1 - const probeCommand = s.splitForFFPMEG(`-v quiet -print_format json -show_format -show_streams -i "${url}"`).join(' ') - exec('ffprobe ' + probeCommand,function(err,stdout,stderr){ - delete(activeProbes[req.params.auth]) - if(err){ - endData.error = (err) - }else{ - endData.ok = true - endData.result = s.parseJSON(stdout) - } - endData.probe = probeCommand - s.closeJsonResponse(res,endData) - }) - },res,req); - }) - /** - * API : ONVIF Method Controller - */ - app.all([ - config.webPaths.apiPrefix+':auth/onvif/:ke/:id/:action', - config.webPaths.apiPrefix+':auth/onvif/:ke/:id/:service/:action' - ],function (req,res){ - var response = {ok:false}; - res.setHeader('Content-Type', 'application/json'); - s.auth(req.params,function(user){ - var errorMessage = function(msg,error){ - response.ok = false - response.msg = msg - response.error = error - res.end(s.prettyPrint(response)) - } - var actionCallback = function(onvifActionResponse){ - response.ok = true - if(onvifActionResponse.data){ - response.responseFromDevice = onvifActionResponse.data - }else{ - response.responseFromDevice = onvifActionResponse - } - if(onvifActionResponse.soap)response.soap = onvifActionResponse.soap - res.end(s.prettyPrint(response)) - } - var isEmpty = function(obj) { - for(var key in obj) { - if(obj.hasOwnProperty(key)) - return false; - } - return true; - } - var doAction = function(Camera){ - var completeAction = function(command){ - if(command.then){ - command.then(actionCallback).catch(function(error){ - errorMessage('Device Action responded with an error',error) - }) - }else if(command){ - response.ok = true - response.repsonseFromDevice = command - res.end(s.prettyPrint(response)) - }else{ - response.error = 'Big Errors, Please report it to Shinobi Development' - res.end(s.prettyPrint(response)) - } - } - var action - if(req.params.service){ - if(Camera.services[req.params.service] === undefined){ - return errorMessage('This is not an available service. Please use one of the following : '+Object.keys(Camera.services).join(', ')) - } - if(Camera.services[req.params.service] === null){ - return errorMessage('This service is not activated. Maybe you are not connected through ONVIF. You can test by attempting to use the "Control" feature with ONVIF in Shinobi.') - } - action = Camera.services[req.params.service][req.params.action] - }else{ - action = Camera[req.params.action] - } - // console.log(s.parseJSON(req.query.options)) - if(!action || typeof action !== 'function'){ - errorMessage(req.params.action+' is not an available ONVIF function. See https://github.com/futomi/node-onvif for functions.') - }else{ - var argNames = s.getFunctionParamNames(action) - var options - var command - if(argNames[0] === 'options' || argNames[0] === 'params'){ - options = {} - if(req.query.options){ - var jsonRevokedText = 'JSON not formated correctly' - try{ - options = JSON.parse(req.query.options) - }catch(err){ - return errorMessage(jsonRevokedText,err) - } - }else if(req.body.options){ - try{ - options = JSON.parse(req.body.options) - }catch(err){ - return errorMessage(jsonRevokedText,err) - } - }else if(req.query.params){ - try{ - options = JSON.parse(req.query.params) - }catch(err){ - return errorMessage(jsonRevokedText,err) - } - }else if(req.body.params){ - try{ - options = JSON.parse(req.body.params) - }catch(err){ - return errorMessage(jsonRevokedText,err) - } - } - } - if(req.params.service){ - command = Camera.services[req.params.service][req.params.action](options) - }else{ - command = Camera[req.params.action](options) - } - completeAction(command) - } - } - if(!s.group[req.params.ke].activeMonitors[req.params.id].onvifConnection){ - //prepeare onvif connection - var controlURL - var monitorConfig = s.group[req.params.ke].rawMonitorConfigurations[req.params.id] - if(!monitorConfig.details.control_base_url||monitorConfig.details.control_base_url===''){ - controlURL = s.buildMonitorUrl(monitorConfig, true) - }else{ - controlURL = monitorConfig.details.control_base_url - } - var controlURLOptions = s.cameraControlOptionsFromUrl(controlURL,monitorConfig) - //create onvif connection - s.group[req.params.ke].activeMonitors[req.params.id].onvifConnection = new onvif.OnvifDevice({ - xaddr : 'http://' + controlURLOptions.host + ':' + controlURLOptions.port + '/onvif/device_service', - user : controlURLOptions.username, - pass : controlURLOptions.password - }) - var device = s.group[req.params.ke].activeMonitors[req.params.id].onvifConnection - device.init().then((info) => { - if(info)doAction(device) - }).catch(function(error){ - return errorMessage('Device responded with an error',error) - }) - }else{ - doAction(s.group[req.params.ke].activeMonitors[req.params.id].onvifConnection) - } - },res,req); - }) - /** * API : Account Edit from Dashboard */ app.all(config.webPaths.apiPrefix+':auth/accounts/:ke/edit',function (req,res){