2019-10-08 04:18:40 -04:00
/ * *
* @ description MeshCentral plugin module
* @ author Ryan Blenis
* @ copyright
* @ license Apache - 2.0
* @ version v0 . 0.1
* /
/*xjslint node: true */
/*xjslint plusplus: true */
/*xjslint maxlen: 256 */
/*jshint node: true */
/*jshint strict: false */
/*jshint esversion: 6 */
"use strict" ;
2019-11-05 00:11:14 -05:00
require ( 'promise' ) ;
2019-10-08 04:18:40 -04:00
2019-11-25 21:06:41 -05:00
/ *
Existing plugins :
https : //raw.githubusercontent.com/ryanblenis/MeshCentral-Sample/master/config.json
https : //raw.githubusercontent.com/ryanblenis/MeshCentral-DevTools/master/config.json
* /
2019-10-08 04:18:40 -04:00
module . exports . pluginHandler = function ( parent ) {
var obj = { } ;
obj . fs = require ( 'fs' ) ;
obj . path = require ( 'path' ) ;
2020-07-06 15:46:38 -04:00
obj . common = require ( './common.js' ) ;
2019-10-08 04:18:40 -04:00
obj . parent = parent ;
obj . pluginPath = obj . parent . path . join ( obj . parent . datapath , 'plugins' ) ;
obj . plugins = { } ;
obj . exports = { } ;
2019-11-01 16:49:18 -04:00
obj . loadList = obj . parent . config . settings . plugins . list ; // For local development / manual install, not from DB
2019-11-25 17:12:43 -05:00
2019-10-10 14:13:25 -04:00
if ( typeof obj . loadList != 'object' ) {
obj . loadList = { } ;
2019-11-25 17:12:43 -05:00
parent . db . getPlugins ( function ( err , plugins ) {
plugins . forEach ( function ( plugin ) {
if ( plugin . status != 1 ) return ;
if ( obj . fs . existsSync ( obj . pluginPath + '/' + plugin . shortName ) ) {
try {
obj . plugins [ plugin . shortName ] = require ( obj . pluginPath + '/' + plugin . shortName + '/' + plugin . shortName + '.js' ) [ plugin . shortName ] ( obj ) ;
obj . exports [ plugin . shortName ] = obj . plugins [ plugin . shortName ] . exports ;
} catch ( e ) {
console . log ( "Error loading plugin: " + plugin . shortName + " (" + e + "). It has been disabled." , e . stack ) ;
}
try { // try loading local info about plugin to database (if it changed locally)
var plugin _config = obj . fs . readFileSync ( obj . pluginPath + '/' + plugin . shortName + '/config.json' ) ;
plugin _config = JSON . parse ( plugin _config ) ;
parent . db . updatePlugin ( plugin . _id , plugin _config ) ;
} catch ( e ) { console . log ( "Plugin config file for " + plugin . name + " could not be parsed." ) ; }
}
} ) ;
2019-11-30 20:26:39 -05:00
obj . parent . updateMeshCore ( ) ; // db calls are async, lets inject here once we're ready
2019-11-01 16:49:18 -04:00
} ) ;
} else {
obj . loadList . forEach ( function ( plugin , index ) {
if ( obj . fs . existsSync ( obj . pluginPath + '/' + plugin ) ) {
try {
obj . plugins [ plugin ] = require ( obj . pluginPath + '/' + plugin + '/' + plugin + '.js' ) [ plugin ] ( obj ) ;
obj . exports [ plugin ] = obj . plugins [ plugin ] . exports ;
} catch ( e ) {
console . log ( "Error loading plugin: " + plugin + " (" + e + "). It has been disabled." , e . stack ) ;
}
2019-10-10 14:13:25 -04:00
}
2019-11-01 16:49:18 -04:00
} ) ;
}
2019-11-25 17:12:43 -05:00
2019-10-10 14:13:25 -04:00
obj . prepExports = function ( ) {
var str = 'function() {\r\n' ;
str += ' var obj = {};\r\n' ;
2019-11-25 17:12:43 -05:00
for ( var p of Object . keys ( obj . plugins ) ) {
2019-11-02 06:27:39 -04:00
str += ' obj.' + p + ' = {};\r\n' ;
2019-11-24 19:19:46 -05:00
if ( Array . isArray ( obj . exports [ p ] ) ) {
2019-11-25 17:12:43 -05:00
for ( var l of Object . values ( obj . exports [ p ] ) ) {
2019-11-24 19:19:46 -05:00
str += ' obj.' + p + '.' + l + ' = ' + obj . plugins [ p ] [ l ] . toString ( ) + '\r\n' ;
}
2019-11-02 06:27:39 -04:00
}
2019-10-09 01:22:01 -04:00
}
2019-10-10 14:13:25 -04:00
2019-11-13 16:31:29 -05:00
str += `
obj . callHook = function ( hookName , ... args ) {
for ( const p of Object . keys ( obj ) ) {
if ( typeof obj [ p ] [ hookName ] == 'function' ) {
2019-11-13 17:48:09 -05:00
obj [ p ] [ hookName ] . apply ( this , args ) ;
2019-10-10 14:13:25 -04:00
}
}
} ;
2019-11-30 20:26:39 -05:00
// accepts a function returning an object or an object with { tabId: "yourTabIdValue", tabTitle: "Your Tab Title" }
2019-10-10 14:13:25 -04:00
obj . registerPluginTab = function ( pluginRegInfo ) {
2019-11-30 20:26:39 -05:00
var d = null ;
if ( typeof pluginRegInfo == 'function' ) d = pluginRegInfo ( ) ;
else d = pluginRegInfo ;
2019-12-02 16:14:04 -05:00
if ( d . tabId == null || d . tabTitle == null ) { return false ; }
2019-10-10 14:13:25 -04:00
if ( ! Q ( d . tabId ) ) {
2019-11-30 20:26:39 -05:00
var defaultOn = 'class="on"' ;
if ( Q ( 'p19headers' ) . querySelectorAll ( "span.on" ) . length ) defaultOn = '' ;
2019-12-11 21:12:39 -05:00
QA ( 'p19headers' , '<span ' + defaultOn + ' id="p19ph-' + d . tabId + '" onclick="return pluginHandler.callPluginPage(\\' '+d.tabId+' \ \ ', this);">' + d . tabTitle + '</span>' ) ;
2019-11-30 20:26:39 -05:00
QA ( 'p19pages' , '<div id="' + d . tabId + '"></div>' ) ;
2019-10-10 14:13:25 -04:00
}
2019-12-02 18:13:19 -05:00
QV ( 'MainDevPlugins' , true ) ;
2019-10-10 14:13:25 -04:00
} ;
2019-11-30 20:26:39 -05:00
obj . callPluginPage = function ( id , el ) {
2019-10-10 14:13:25 -04:00
var pages = Q ( 'p19pages' ) . querySelectorAll ( "#p19pages>div" ) ;
for ( const i of pages ) { i . style . display = 'none' ; }
QV ( id , true ) ;
2019-11-30 20:26:39 -05:00
var tabs = Q ( 'p19headers' ) . querySelectorAll ( "span" ) ;
for ( const i of tabs ) { i . classList . remove ( 'on' ) ; }
el . classList . add ( 'on' ) ;
2019-12-11 21:12:39 -05:00
putstore ( '_curPluginPage' , id ) ;
2019-10-10 14:13:25 -04:00
} ;
2019-10-30 04:17:17 -04:00
obj . addPluginEx = function ( ) {
meshserver . send ( { action : 'addplugin' , url : Q ( 'pluginurlinput' ) . value } ) ;
} ;
obj . addPluginDlg = function ( ) {
2019-11-25 17:12:43 -05:00
setDialogMode ( 2 , "Plugin Download URL" , 3 , obj . addPluginEx , '<p><b>WARNING:</b> Downloading plugins may compromise server security. Only download from trusted sources.</p><input type=text id=pluginurlinput style=width:100% placeholder="https://" />' ) ;
2019-10-30 04:17:17 -04:00
focusTextBox ( 'pluginurlinput' ) ;
} ;
2019-11-22 14:25:13 -05:00
obj . refreshPluginHandler = function ( ) {
let st = document . createElement ( 'script' ) ;
st . src = '/pluginHandler.js' ;
document . body . appendChild ( st ) ;
} ;
2020-05-12 09:24:43 -04:00
return obj ; } ` ;
2019-10-10 14:13:25 -04:00
return str ;
2019-10-08 04:18:40 -04:00
}
2019-11-25 17:12:43 -05:00
obj . refreshJS = function ( req , res ) {
2019-11-22 14:25:13 -05:00
// to minimize server reboots when installing new plugins, we call the new data and overwrite the old pluginHandler on the front end
res . set ( 'Content-Type' , 'text/javascript' ) ;
2020-05-25 03:09:35 -04:00
res . send ( 'pluginHandlerBuilder = ' + obj . prepExports ( ) + '\r\n' + ' pluginHandler = new pluginHandlerBuilder(); pluginHandler.callHook("onWebUIStartupEnd");' ) ;
2019-11-22 14:25:13 -05:00
}
2019-11-25 17:12:43 -05:00
2019-10-10 14:13:25 -04:00
obj . callHook = function ( hookName , ... args ) {
for ( var p in obj . plugins ) {
if ( typeof obj . plugins [ p ] [ hookName ] == 'function' ) {
try {
2019-12-26 10:45:14 -05:00
obj . plugins [ p ] [ hookName ] ( ... args ) ;
2019-10-10 14:13:25 -04:00
} catch ( e ) {
2019-11-25 17:12:43 -05:00
console . log ( "Error ocurred while running plugin hook" + p + ':' + hookName + ' (' + e + ')' ) ;
2019-10-10 14:13:25 -04:00
}
}
}
2019-10-08 04:18:40 -04:00
} ;
2019-10-10 14:13:25 -04:00
obj . addMeshCoreModules = function ( modulesAdd ) {
for ( var plugin in obj . plugins ) {
var moduleDirPath = null ;
var modulesDir = null ;
//if (obj.args.minifycore !== false) { try { moduleDirPath = obj.path.join(obj.pluginPath, 'modules_meshcore_min'); modulesDir = obj.fs.readdirSync(moduleDirPath); } catch (e) { } } // Favor minified modules if present.
if ( modulesDir == null ) { try { moduleDirPath = obj . path . join ( obj . pluginPath , plugin + '/modules_meshcore' ) ; modulesDir = obj . fs . readdirSync ( moduleDirPath ) ; } catch ( e ) { } } // Use non-minified mofules.
if ( modulesDir != null ) {
for ( var i in modulesDir ) {
if ( modulesDir [ i ] . toLowerCase ( ) . endsWith ( '.js' ) ) {
var moduleName = modulesDir [ i ] . substring ( 0 , modulesDir [ i ] . length - 3 ) ;
if ( moduleName . endsWith ( '.min' ) ) { moduleName = moduleName . substring ( 0 , moduleName . length - 4 ) ; } // Remove the ".min" for ".min.js" files.
var moduleData = [ 'try { addModule("' , moduleName , '", "' , obj . parent . escapeCodeString ( obj . fs . readFileSync ( obj . path . join ( moduleDirPath , modulesDir [ i ] ) ) . toString ( 'binary' ) ) , '"); addedModules.push("' , moduleName , '"); } catch (e) { }\r\n' ] ;
// Merge this module
// NOTE: "smbios" module makes some non-AI Linux segfault, only include for IA platforms.
if ( moduleName . startsWith ( 'amt-' ) || ( moduleName == 'smbios' ) ) {
// Add to IA / Intel AMT cores only
modulesAdd [ 'windows-amt' ] . push ( ... moduleData ) ;
modulesAdd [ 'linux-amt' ] . push ( ... moduleData ) ;
} else if ( moduleName . startsWith ( 'win-' ) ) {
// Add to Windows cores only
modulesAdd [ 'windows-amt' ] . push ( ... moduleData ) ;
} else if ( moduleName . startsWith ( 'linux-' ) ) {
// Add to Linux cores only
modulesAdd [ 'linux-amt' ] . push ( ... moduleData ) ;
modulesAdd [ 'linux-noamt' ] . push ( ... moduleData ) ;
} else {
// Add to all cores
modulesAdd [ 'windows-amt' ] . push ( ... moduleData ) ;
modulesAdd [ 'linux-amt' ] . push ( ... moduleData ) ;
modulesAdd [ 'linux-noamt' ] . push ( ... moduleData ) ;
}
// Merge this module to recovery modules if needed
if ( modulesAdd [ 'windows-recovery' ] != null ) {
if ( ( moduleName == 'win-console' ) || ( moduleName == 'win-message-pump' ) || ( moduleName == 'win-terminal' ) ) {
modulesAdd [ 'windows-recovery' ] . push ( ... moduleData ) ;
}
}
2019-10-08 04:18:40 -04:00
2019-10-10 14:13:25 -04:00
// Merge this module to agent recovery modules if needed
if ( modulesAdd [ 'windows-agentrecovery' ] != null ) {
if ( ( moduleName == 'win-console' ) || ( moduleName == 'win-message-pump' ) || ( moduleName == 'win-terminal' ) ) {
modulesAdd [ 'windows-agentrecovery' ] . push ( ... moduleData ) ;
}
}
}
}
2019-10-08 04:18:40 -04:00
}
2019-10-10 14:13:25 -04:00
}
2019-10-08 04:18:40 -04:00
} ;
2019-10-10 14:13:25 -04:00
obj . deviceViewPanel = function ( ) {
var panel = { } ;
for ( var p in obj . plugins ) {
if ( typeof obj . plugins [ p ] [ hookName ] == 'function' ) {
try {
panel [ p ] . header = obj . plugins [ p ] . on _device _header ( ) ;
panel [ p ] . content = obj . plugins [ p ] . on _device _page ( ) ;
} catch ( e ) {
2019-11-25 17:12:43 -05:00
console . log ( "Error ocurred while getting plugin views " + p + ':' + ' (' + e + ')' ) ;
2019-10-10 14:13:25 -04:00
}
}
}
return panel ;
2019-10-30 04:17:17 -04:00
} ;
2019-11-25 17:12:43 -05:00
obj . isValidConfig = function ( conf , url ) { // check for the required attributes
2019-10-30 04:17:17 -04:00
var isValid = true ;
if ( ! (
typeof conf . name == 'string'
2019-11-01 16:49:18 -04:00
&& typeof conf . shortName == 'string'
2019-10-30 04:17:17 -04:00
&& typeof conf . version == 'string'
2019-11-25 17:12:43 -05:00
// && typeof conf.author == 'string'
2019-10-30 04:17:17 -04:00
&& typeof conf . description == 'string'
&& typeof conf . hasAdminPanel == 'boolean'
&& typeof conf . homepage == 'string'
&& typeof conf . changelogUrl == 'string'
&& typeof conf . configUrl == 'string'
&& typeof conf . repository == 'object'
&& typeof conf . repository . type == 'string'
&& typeof conf . repository . url == 'string'
&& typeof conf . meshCentralCompat == 'string'
2019-11-25 17:12:43 -05:00
// && conf.configUrl == url // make sure we're loading a plugin from its desired config
2019-10-30 04:17:17 -04:00
) ) isValid = false ;
// more checks here?
2019-11-01 16:49:18 -04:00
if ( conf . repository . type == 'git' ) {
if ( typeof conf . downloadUrl != 'string' ) isValid = false ;
}
2019-10-30 04:17:17 -04:00
return isValid ;
} ;
2019-11-25 17:12:43 -05:00
// https://raw.githubusercontent.com/ryanblenis/MeshCentral-Sample/master/config.json
obj . getPluginConfig = function ( configUrl ) {
return new Promise ( function ( resolve , reject ) {
var http = ( configUrl . indexOf ( 'https://' ) >= 0 ) ? require ( 'https' ) : require ( 'http' ) ;
if ( configUrl . indexOf ( '://' ) === - 1 ) reject ( "Unable to fetch the config: Bad URL (" + configUrl + ")" ) ;
var options = require ( 'url' ) . parse ( configUrl ) ;
if ( typeof parent . config . settings . plugins . proxy == 'string' ) { // Proxy support
const HttpsProxyAgent = require ( 'https-proxy-agent' ) ;
options . agent = new HttpsProxyAgent ( require ( 'url' ) . parse ( parent . config . settings . plugins . proxy ) ) ;
2019-11-11 10:47:30 -05:00
}
2019-11-25 17:12:43 -05:00
http . get ( options , function ( res ) {
var configStr = '' ;
res . on ( 'data' , function ( chunk ) {
configStr += chunk ;
} ) ;
res . on ( 'end' , function ( ) {
if ( configStr [ 0 ] == '{' ) { // Let's be sure we're JSON
try {
var pluginConfig = JSON . parse ( configStr ) ;
if ( Array . isArray ( pluginConfig ) && pluginConfig . length == 1 ) pluginConfig = pluginConfig [ 0 ] ;
if ( obj . isValidConfig ( pluginConfig , configUrl ) ) {
resolve ( pluginConfig ) ;
} else {
reject ( "This does not appear to be a valid plugin configuration." ) ;
}
} catch ( e ) { reject ( "Error getting plugin config. Check that you have valid JSON." ) ; }
} else {
reject ( "Error getting plugin config. Check that you have valid JSON." ) ;
}
} ) ;
} ) . on ( 'error' , function ( e ) {
2019-11-05 00:11:14 -05:00
reject ( "Error getting plugin config: " + e . message ) ;
2019-11-25 17:12:43 -05:00
} ) ;
2019-11-05 00:11:14 -05:00
} )
2019-11-01 16:49:18 -04:00
} ;
2019-11-25 17:12:43 -05:00
2020-04-20 12:24:07 -04:00
// MeshCentral now adheres to semver, drop the -<alpha> off the version number for later versions for comparing plugins prior to this change
obj . versionToNumber = function ( ver ) { var x = ver . split ( '-' ) ; if ( x . length != 2 ) return ver ; return x [ 0 ] ; }
2020-03-11 19:53:09 -04:00
// Check if the current version of MeshCentral is at least the minimal required.
2020-03-11 21:22:06 -04:00
obj . versionCompare = function ( current , minimal ) {
2020-03-11 19:53:09 -04:00
if ( minimal . startsWith ( '>=' ) ) { minimal = minimal . substring ( 2 ) ; }
2020-03-11 21:22:06 -04:00
var c = obj . versionToNumber ( current ) . split ( '.' ) , m = obj . versionToNumber ( minimal ) . split ( '.' ) ;
2020-03-11 19:53:09 -04:00
if ( c . length != m . length ) return false ;
for ( var i = 0 ; i < c . length ; i ++ ) { var cx = parseInt ( c [ i ] ) , cm = parseInt ( m [ i ] ) ; if ( cx > cm ) { return true ; } if ( cx < cm ) { return false ; } }
return true ;
}
2019-11-25 17:12:43 -05:00
obj . getPluginLatest = function ( ) {
return new Promise ( function ( resolve , reject ) {
parent . db . getPlugins ( function ( err , plugins ) {
2019-11-05 00:11:14 -05:00
var proms = [ ] ;
2019-11-25 17:12:43 -05:00
plugins . forEach ( function ( curconf ) {
proms . push ( obj . getPluginConfig ( curconf . configUrl ) . catch ( e => { return null ; } ) ) ;
2019-11-05 00:11:14 -05:00
} ) ;
var latestRet = [ ] ;
2019-11-25 17:12:43 -05:00
Promise . all ( proms ) . then ( function ( newconfs ) {
2019-11-24 21:29:13 -05:00
var nconfs = [ ] ;
2019-11-25 17:12:43 -05:00
// Filter out config download issues
newconfs . forEach ( function ( nc ) { if ( nc !== null ) nconfs . push ( nc ) ; } ) ;
if ( nconfs . length == 0 ) { resolve ( [ ] ) ; } else {
nconfs . forEach ( function ( newconf ) {
var curconf = null ;
plugins . forEach ( function ( conf ) {
if ( conf . configUrl == newconf . configUrl ) curconf = conf ;
} ) ;
if ( curconf == null ) reject ( "Some plugin configs could not be parsed" ) ;
var s = require ( 'semver' ) ;
latestRet . push ( {
'id' : curconf . _id ,
'installedVersion' : curconf . version ,
'version' : newconf . version ,
'hasUpdate' : s . gt ( newconf . version , curconf . version ) ,
2020-03-11 21:22:06 -04:00
'meshCentralCompat' : obj . versionCompare ( parent . currentVer , newconf . meshCentralCompat ) ,
2019-11-25 17:12:43 -05:00
'changelogUrl' : curconf . changelogUrl ,
'status' : curconf . status
} ) ;
resolve ( latestRet ) ;
2019-11-05 00:11:14 -05:00
} ) ;
2019-11-25 17:12:43 -05:00
}
} ) . catch ( ( e ) => { console . log ( "Error reaching plugins, update call aborted." , e ) } ) ;
2019-11-05 00:11:14 -05:00
} ) ;
2019-11-01 16:49:18 -04:00
} ) ;
} ;
2019-11-25 17:12:43 -05:00
obj . addPlugin = function ( pluginConfig ) {
return new Promise ( function ( resolve , reject ) {
parent . db . addPlugin ( {
'name' : pluginConfig . name ,
'shortName' : pluginConfig . shortName ,
'version' : pluginConfig . version ,
'description' : pluginConfig . description ,
'hasAdminPanel' : pluginConfig . hasAdminPanel ,
'homepage' : pluginConfig . homepage ,
'changelogUrl' : pluginConfig . changelogUrl ,
'configUrl' : pluginConfig . configUrl ,
'downloadUrl' : pluginConfig . downloadUrl ,
'repository' : {
'type' : pluginConfig . repository . type ,
'url' : pluginConfig . repository . url
} ,
'meshCentralCompat' : pluginConfig . meshCentralCompat ,
'versionHistoryUrl' : pluginConfig . versionHistoryUrl ,
'status' : 0 // 0: disabled, 1: enabled
} , function ( ) {
parent . db . getPlugins ( function ( err , docs ) {
if ( err ) reject ( err ) ;
else resolve ( docs ) ;
2019-11-05 00:11:14 -05:00
} ) ;
} ) ;
} ) ;
2019-11-01 16:49:18 -04:00
} ;
2019-11-25 17:12:43 -05:00
obj . installPlugin = function ( id , version _only , force _url , func ) {
parent . db . getPlugin ( id , function ( err , docs ) {
2019-11-01 16:49:18 -04:00
// the "id" would probably suffice, but is probably an sanitary issue, generate a random instead
var randId = Math . random ( ) . toString ( 32 ) . replace ( '0.' , '' ) ;
2019-11-25 17:12:43 -05:00
var fileName = obj . parent . path . join ( require ( 'os' ) . tmpdir ( ) , 'Plugin_' + randId + '.zip' ) ;
2019-11-01 16:49:18 -04:00
var plugin = docs [ 0 ] ;
2019-11-25 17:12:43 -05:00
if ( plugin . repository . type == 'git' ) {
2019-11-01 16:49:18 -04:00
const file = obj . fs . createWriteStream ( fileName ) ;
2019-11-22 14:25:13 -05:00
var dl _url = plugin . downloadUrl ;
if ( version _only != null && version _only != false ) dl _url = version _only . url ;
2019-11-22 21:17:07 -05:00
if ( force _url != null ) dl _url = force _url ;
2019-11-22 14:25:13 -05:00
var url = require ( 'url' ) ;
var q = url . parse ( dl _url , true ) ;
var http = ( q . protocol == "http" ) ? require ( 'http' ) : require ( 'https' ) ;
var opts = {
2019-11-25 17:12:43 -05:00
path : q . pathname ,
2019-11-22 14:25:13 -05:00
host : q . hostname ,
port : q . port ,
headers : {
'User-Agent' : 'MeshCentral'
} ,
followRedirects : true ,
method : 'GET'
} ;
2019-11-25 17:12:43 -05:00
if ( typeof parent . config . settings . plugins . proxy == 'string' ) { // Proxy support
const HttpsProxyAgent = require ( 'https-proxy-agent' ) ;
opts . agent = new HttpsProxyAgent ( require ( 'url' ) . parse ( parent . config . settings . plugins . proxy ) ) ;
}
var request = http . get ( opts , function ( response ) {
2019-11-22 14:25:13 -05:00
// handle redirections with grace
2019-11-22 21:17:07 -05:00
if ( response . headers . location ) return obj . installPlugin ( id , version _only , response . headers . location , func ) ;
2019-11-01 16:49:18 -04:00
response . pipe ( file ) ;
2019-11-25 17:12:43 -05:00
file . on ( 'finish' , function ( ) {
file . close ( function ( ) {
2020-03-11 19:53:09 -04:00
var yauzl = require ( 'yauzl' ) ;
2019-11-01 16:49:18 -04:00
if ( ! obj . fs . existsSync ( obj . pluginPath ) ) {
obj . fs . mkdirSync ( obj . pluginPath ) ;
}
if ( ! obj . fs . existsSync ( obj . parent . path . join ( obj . pluginPath , plugin . shortName ) ) ) {
obj . fs . mkdirSync ( obj . parent . path . join ( obj . pluginPath , plugin . shortName ) ) ;
}
yauzl . open ( fileName , { lazyEntries : true } , function ( err , zipfile ) {
if ( err ) throw err ;
zipfile . readEntry ( ) ;
2019-11-25 17:12:43 -05:00
zipfile . on ( 'entry' , function ( entry ) {
2019-11-01 16:49:18 -04:00
let pluginPath = obj . parent . path . join ( obj . pluginPath , plugin . shortName ) ;
let pathReg = new RegExp ( /(.*?\/)/ ) ;
2019-11-25 17:12:43 -05:00
//if (process.platform == 'win32') { pathReg = new RegExp(/(.*?\\/); }
2019-11-01 16:49:18 -04:00
let filePath = obj . parent . path . join ( pluginPath , entry . fileName . replace ( pathReg , '' ) ) ; // remove top level dir
2019-11-25 17:12:43 -05:00
2019-11-01 16:49:18 -04:00
if ( /\/$/ . test ( entry . fileName ) ) { // dir
if ( ! obj . fs . existsSync ( filePath ) )
obj . fs . mkdirSync ( filePath ) ;
zipfile . readEntry ( ) ;
} else { // file
zipfile . openReadStream ( entry , function ( err , readStream ) {
if ( err ) throw err ;
2019-11-25 17:12:43 -05:00
readStream . on ( 'end' , function ( ) { zipfile . readEntry ( ) ; } ) ;
2019-11-01 16:49:18 -04:00
readStream . pipe ( obj . fs . createWriteStream ( filePath ) ) ;
} ) ;
}
} ) ;
2019-11-25 17:12:43 -05:00
zipfile . on ( 'end' , function ( ) {
setTimeout ( function ( ) {
obj . fs . unlinkSync ( fileName ) ;
if ( version _only == null || version _only === false ) {
parent . db . setPluginStatus ( id , 1 , func ) ;
} else {
parent . db . updatePlugin ( id , { status : 1 , version : version _only . name } , func ) ;
}
2019-12-03 21:49:06 -05:00
try {
obj . plugins [ plugin . shortName ] = require ( obj . pluginPath + '/' + plugin . shortName + '/' + plugin . shortName + '.js' ) [ plugin . shortName ] ( obj ) ;
obj . exports [ plugin . shortName ] = obj . plugins [ plugin . shortName ] . exports ;
if ( typeof obj . plugins [ plugin . shortName ] . server _startup == 'function' ) obj . plugins [ plugin . shortName ] . server _startup ( ) ;
} catch ( e ) { console . log ( 'Error instantiating new plugin: ' , e ) ; }
try {
var plugin _config = obj . fs . readFileSync ( obj . pluginPath + '/' + plugin . shortName + '/config.json' ) ;
plugin _config = JSON . parse ( plugin _config ) ;
parent . db . updatePlugin ( plugin . _id , plugin _config ) ;
} catch ( e ) { console . log ( 'Error reading plugin config upon install' ) ; }
2019-11-25 17:12:43 -05:00
parent . updateMeshCore ( ) ;
} ) ;
} ) ;
2019-11-01 16:49:18 -04:00
} ) ;
2019-10-30 04:17:17 -04:00
} ) ;
2019-11-01 16:49:18 -04:00
} ) ;
} ) ;
2019-11-25 17:12:43 -05:00
} else if ( plugin . repository . type == 'npm' ) {
2019-11-22 14:25:13 -05:00
// @TODO npm support? (need a test plugin)
2019-10-30 04:17:17 -04:00
}
2019-11-01 16:49:18 -04:00
} ) ;
} ;
2019-11-25 17:12:43 -05:00
obj . getPluginVersions = function ( id ) {
return new Promise ( function ( resolve , reject ) {
parent . db . getPlugin ( id , function ( err , docs ) {
2019-11-22 14:25:13 -05:00
var plugin = docs [ 0 ] ;
2019-11-25 17:12:43 -05:00
if ( plugin . versionHistoryUrl == null ) reject ( "No version history available for this plugin." ) ;
2019-11-22 14:25:13 -05:00
var url = require ( 'url' ) ;
var q = url . parse ( plugin . versionHistoryUrl , true ) ;
2019-11-25 17:12:43 -05:00
var http = ( q . protocol == 'http' ) ? require ( 'http' ) : require ( 'https' ) ;
2019-11-22 14:25:13 -05:00
var opts = {
2019-11-25 17:12:43 -05:00
path : q . pathname ,
2019-11-22 14:25:13 -05:00
host : q . hostname ,
port : q . port ,
headers : {
'User-Agent' : 'MeshCentral' ,
'Accept' : 'application/vnd.github.v3+json'
}
} ;
2019-11-25 17:12:43 -05:00
if ( typeof parent . config . settings . plugins . proxy == 'string' ) { // Proxy support
const HttpsProxyAgent = require ( 'https-proxy-agent' ) ;
options . agent = new HttpsProxyAgent ( require ( 'url' ) . parse ( parent . config . settings . plugins . proxy ) ) ;
}
http . get ( opts , function ( res ) {
2019-11-22 14:25:13 -05:00
var versStr = '' ;
2019-11-25 17:12:43 -05:00
res . on ( 'data' , function ( chunk ) {
2019-11-22 14:25:13 -05:00
versStr += chunk ;
} ) ;
2019-11-25 17:12:43 -05:00
res . on ( 'end' , function ( ) {
2020-07-06 15:46:38 -04:00
if ( ( versStr [ 0 ] == '{' ) || ( versStr [ 0 ] == '[' ) ) { // let's be sure we're JSON
2019-11-22 14:25:13 -05:00
try {
var vers = JSON . parse ( versStr ) ;
var vList = [ ] ;
var s = require ( 'semver' ) ;
vers . forEach ( ( v ) => {
2019-11-25 17:12:43 -05:00
if ( s . lt ( v . name , plugin . version ) ) vList . push ( v ) ;
2019-11-22 14:25:13 -05:00
} ) ;
2019-11-25 17:12:43 -05:00
if ( vers . length == 0 ) reject ( "No previous versions available." ) ;
2019-11-22 14:25:13 -05:00
resolve ( { 'id' : plugin . _id , 'name' : plugin . name , versionList : vList } ) ;
2019-11-25 17:12:43 -05:00
} catch ( e ) { reject ( "Version history problem." ) ; }
2019-11-22 14:25:13 -05:00
} else {
2019-11-25 17:12:43 -05:00
reject ( "Version history appears to be malformed." + versStr ) ;
2019-11-22 14:25:13 -05:00
}
} ) ;
2019-11-25 17:12:43 -05:00
} ) . on ( 'error' , function ( e ) {
2019-11-22 14:25:13 -05:00
reject ( "Error getting plugin versions: " + e . message ) ;
2019-11-25 17:12:43 -05:00
} ) ;
2019-11-22 14:25:13 -05:00
} ) ;
} ) ;
} ;
2019-11-25 17:12:43 -05:00
obj . disablePlugin = function ( id , func ) {
parent . db . getPlugin ( id , function ( err , docs ) {
2019-11-22 14:25:13 -05:00
var plugin = docs [ 0 ] ;
parent . db . setPluginStatus ( id , 0 , func ) ;
delete obj . plugins [ plugin . shortName ] ;
delete obj . exports [ plugin . shortName ] ;
2019-12-04 21:36:53 -05:00
parent . updateMeshCore ( ) ;
2019-11-22 14:25:13 -05:00
} ) ;
2019-11-01 16:49:18 -04:00
} ;
2019-11-25 17:12:43 -05:00
obj . removePlugin = function ( id , func ) {
parent . db . getPlugin ( id , function ( err , docs ) {
2019-11-01 16:49:18 -04:00
var plugin = docs [ 0 ] ;
2020-03-13 23:39:21 -04:00
var rimraf = require ( 'rimraf' ) ;
2019-11-01 16:49:18 -04:00
let pluginPath = obj . parent . path . join ( obj . pluginPath , plugin . shortName ) ;
2020-03-13 23:39:21 -04:00
rimraf . sync ( pluginPath ) ;
2019-11-01 16:49:18 -04:00
parent . db . deletePlugin ( id , func ) ;
delete obj . plugins [ plugin . shortName ] ;
2019-10-30 04:17:17 -04:00
} ) ;
} ;
2019-11-25 17:12:43 -05:00
2019-11-05 04:17:10 -05:00
obj . handleAdminReq = function ( req , res , user , serv ) {
2020-07-06 18:04:22 -04:00
if ( ( req . query . pin == null ) || ( obj . common . isAlphaNumeric ( req . query . pin ) !== true ) ) { res . sendStatus ( 401 ) ; return ; }
2019-11-02 06:27:39 -04:00
var path = obj . path . join ( obj . pluginPath , req . query . pin , 'views' ) ;
2020-07-08 20:18:59 -04:00
// path isn't a filename, it is a folder path
//if (obj.common.IsFilenameValid(path) !== true) { res.sendStatus(401); return; }
2019-11-02 06:27:39 -04:00
serv . app . set ( 'views' , path ) ;
2020-07-06 15:46:38 -04:00
if ( ( obj . plugins [ req . query . pin ] != null ) && ( typeof obj . plugins [ req . query . pin ] . handleAdminReq == 'function' ) ) {
2019-11-05 04:17:10 -05:00
obj . plugins [ req . query . pin ] . handleAdminReq ( req , res , user ) ;
2019-11-25 17:12:43 -05:00
} else {
2019-11-02 06:27:39 -04:00
res . sendStatus ( 401 ) ;
}
}
2019-11-25 17:12:43 -05:00
obj . handleAdminPostReq = function ( req , res , user , serv ) {
2020-07-06 18:04:22 -04:00
if ( ( req . query . pin == null ) || ( obj . common . isAlphaNumeric ( req . query . pin ) !== true ) ) { res . sendStatus ( 401 ) ; return ; }
2019-11-02 06:27:39 -04:00
var path = obj . path . join ( obj . pluginPath , req . query . pin , 'views' ) ;
2020-07-08 20:18:59 -04:00
// path isn't a filename, it is a folder path
//if (obj.common.IsFilenameValid(path) !== true) { res.sendStatus(401); return; }
2019-11-02 06:27:39 -04:00
serv . app . set ( 'views' , path ) ;
2020-07-06 15:46:38 -04:00
if ( ( obj . plugins [ req . query . pin ] != null ) && ( typeof obj . plugins [ req . query . pin ] . handleAdminPostReq == 'function' ) ) {
2019-11-05 04:17:10 -05:00
obj . plugins [ req . query . pin ] . handleAdminPostReq ( req , res , user ) ;
2019-11-25 17:12:43 -05:00
} else {
2019-11-02 06:27:39 -04:00
res . sendStatus ( 401 ) ;
}
}
2019-10-08 04:18:40 -04:00
return obj ;
} ;