diff --git a/package-lock.json b/package-lock.json index 684a93a..68d483a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,8 +18,6 @@ "node-fetch": "^2.6.1 <2.6.8", "progress-stream": "^2.0.0", "regenerator-runtime": "^0.13.7", - "shell-escape": "^0.2.0", - "ssh2": "^0.8.9", "stream.pipeline-shim": "^1.1.0", "webos-service": "git+https://github.com/webosose/nodejs-module-webos-service.git#59172863e61abb53b2f3f174ab9a23c655819188" }, @@ -7514,6 +7512,8 @@ "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "optional": true, "dependencies": { "safer-buffer": "~2.1.0" } @@ -8476,6 +8476,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "optional": true, "dependencies": { "tweetnacl": "^0.14.3" } @@ -15535,7 +15537,8 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "node_modules/schema-utils": { "version": "2.7.1", @@ -15708,11 +15711,6 @@ "node": ">=8" } }, - "node_modules/shell-escape": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/shell-escape/-/shell-escape-0.2.0.tgz", - "integrity": "sha512-uRRBT2MfEOyxuECseCZd28jC1AJ8hmqqneWQ4VWUTgCAFvb3wKU1jLqj6egC4Exrr88ogg3dp+zroH4wJuaXzw==" - }, "node_modules/shell-quote": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", @@ -16064,30 +16062,6 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, - "node_modules/ssh2": { - "version": "0.8.9", - "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-0.8.9.tgz", - "integrity": "sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw==", - "dependencies": { - "ssh2-streams": "~0.4.10" - }, - "engines": { - "node": ">=5.2.0" - } - }, - "node_modules/ssh2-streams": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/ssh2-streams/-/ssh2-streams-0.4.10.tgz", - "integrity": "sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ==", - "dependencies": { - "asn1": "~0.2.0", - "bcrypt-pbkdf": "^1.0.2", - "streamsearch": "~0.1.2" - }, - "engines": { - "node": ">=5.2.0" - } - }, "node_modules/sshpk": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", @@ -16233,14 +16207,6 @@ "stream.finished": "^1.2.0" } }, - "node_modules/streamsearch": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", - "integrity": "sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA==", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -16856,7 +16822,9 @@ "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true, + "optional": true }, "node_modules/type-check": { "version": "0.4.0", diff --git a/package.json b/package.json index 11ea329..30a98e0 100644 --- a/package.json +++ b/package.json @@ -81,8 +81,6 @@ "node-fetch": "^2.6.1 <2.6.8", "progress-stream": "^2.0.0", "regenerator-runtime": "^0.13.7", - "shell-escape": "^0.2.0", - "ssh2": "^0.8.9", "stream.pipeline-shim": "^1.1.0", "webos-service": "git+https://github.com/webosose/nodejs-module-webos-service.git#59172863e61abb53b2f3f174ab9a23c655819188" }, diff --git a/services/service.ts b/services/service.ts index b5e4757..cc11f4b 100644 --- a/services/service.ts +++ b/services/service.ts @@ -312,7 +312,7 @@ function tryRespond>(runner: (message: Message) => function runService() { const service = new Service(serviceInfo.id); - const serviceRemote = new ServiceRemote(service); + const serviceRemote = new ServiceRemote(); service.activityManager.idleTimeout = 30; @@ -699,7 +699,7 @@ if (process.argv[2] === 'self-update') { }); (async () => { - const service = new ServiceRemote(null) as Service; + const service = new ServiceRemote() as Service; try { await createToast('Performing self-update (inner)', service); const installedPackageId = await installPackage(process.argv[3], service); diff --git a/services/webos-service-remote/execbus.js b/services/webos-service-remote/execbus.js index c486859..ff4ae54 100644 --- a/services/webos-service-remote/execbus.js +++ b/services/webos-service-remote/execbus.js @@ -16,6 +16,15 @@ class Request extends EventEmitter { } export default class Handle { + constructor(usePublic) { + if (typeof usePublic === 'boolean') { + this.usePublic = usePublic; + } else { + /* use luna-send-pub when not root */ + this.usePublic = process.uid !== 0; + } + } + /** * Send a message via luna-send and start interactive session * @param {string} uri @@ -24,10 +33,14 @@ export default class Handle { */ // eslint-disable-next-line class-methods-use-this subscribe(uri, payload) { + const command = this.usePublic ? 'luna-send-pub' : 'luna-send'; + /* using -a with luna-send-pub causes an error */ + const args = [...(this.usePublic ? [] : ['-a', 'webosbrew']), '-i', uri, payload]; + let process; let request; try { - process = spawn('luna-send', ['-a', 'webosbrew', '-i', uri, payload]); + process = spawn(command, args); request = new Request(process); const stream = process.stdout; let stdout = ''; @@ -55,7 +68,7 @@ export default class Handle { JSON.stringify({ returnValue: false, errorCode: -1, - errorText: `Unable to exec luna-send: ${err}`, + errorText: `Unable to exec ${command}: ${err}`, }), false, ), diff --git a/services/webos-service-remote/service.js b/services/webos-service-remote/service.js index f7047bd..daed069 100644 --- a/services/webos-service-remote/service.js +++ b/services/webos-service-remote/service.js @@ -1,22 +1,13 @@ import Message from './message'; -import SSHHandle from './sshbus'; import ExecHandle from './execbus'; import Subscription from './subscription'; /** - * Drop in replacement for webos-service, but using SSH shell to call luna-send-pub instead. + * Drop in replacement for webos-service, but luna-send(-pub) instead. */ export default class Service { - /** - * - * @param {Service|null} service from webos-service module. Used for retreving SSH pubkey passphrase. If null is provided, alternative backend using root execution is used - */ - constructor(service) { - if (service === null) { - this.sendingHandle = new ExecHandle(); - } else { - this.sendingHandle = new SSHHandle(service); - } + constructor() { + this.sendingHandle = new ExecHandle(); } /* Call a service on the bus diff --git a/services/webos-service-remote/ssh-promise.js b/services/webos-service-remote/ssh-promise.js deleted file mode 100644 index 53c6581..0000000 --- a/services/webos-service-remote/ssh-promise.js +++ /dev/null @@ -1,71 +0,0 @@ -import { Client } from 'ssh2'; - -import shellEscape from 'shell-escape'; - -export default class SSH { - constructor() { - this.client = new Client(); - } - - connect(config) { - const ssh = this; - return new Promise((resolve, reject) => { - this.client.on('ready', () => { - this.client.removeListener('error', reject); - resolve(ssh); - }); - this.client.on('error', reject); - this.client.connect(config); - }); - } - - close() { - this.client.end(); - } - - /** - * - * @param {string} cmd Executable path/command - * @param {string[]} args Program arguments - * @returns Promise to shell stream - */ - spawn(cmd, args) { - return new Promise((resolve, reject) => { - this.client.exec(`${cmd} ${args ? shellEscape(args) : ''}`, (err, stream) => { - if (err) { - reject(err); - return; - } - resolve(stream); - }); - }); - } - - /** - * - * @param {string} cmd Executable path/command - * @param {string[]} args Program arguments - * @returns Promise to command output - */ - exec(cmd, args) { - const { client } = this; - return new Promise((resolve, reject) => { - client.exec(`${cmd} ${args ? shellEscape(args) : ''}`, (err, channel) => { - if (err) { - reject(err); - return; - } - const output = { stdout: '', stderr: '' }; - channel.on('data', (chunk) => { - output.stdout += chunk.toString(); - }); - channel.stderr.on('data', (chunk) => { - output.stderr += chunk.toString(); - }); - channel.on('close', () => { - resolve(output); - }); - }); - }); - } -} diff --git a/services/webos-service-remote/sshbus.js b/services/webos-service-remote/sshbus.js deleted file mode 100644 index 45b1e65..0000000 --- a/services/webos-service-remote/sshbus.js +++ /dev/null @@ -1,141 +0,0 @@ -/* eslint-disable max-classes-per-file, no-underscore-dangle */ -import { EventEmitter } from 'events'; -import { readFileSync } from 'fs'; -import { asyncReadFile } from '../adapter'; -import Message from './message'; -import SSH from './ssh-promise'; - -class Request extends EventEmitter { - constructor(ssh) { - super(); - this.ssh = ssh; - } - - cancel() { - this.ssh.close(); - } -} - -export default class Handle { - constructor(service) { - this.service = service; - } - - /** - * Send a message via luna-send-pub and start interactive session - * @param {string} uri - * @param {string} payload - * @returns EventEmitter of this session - */ - subscribe(uri, payload) { - const ssh = new SSH(); - const request = new Request(ssh); - this._getSshConfig() - .then((config) => ssh.connect(config)) - .then(() => ssh.spawn('luna-send-pub', ['-i', uri, payload])) - .then((stream) => { - let stdout = ''; - stream.on('close', () => {}); - stream.on('data', (data) => { - stdout += data.toString(); - let searchPos = 0; - let breakPos = 0; - // eslint-disable-next-line no-cond-assign - while ((breakPos = stdout.indexOf('\n', searchPos)) > 0) { - const line = stdout.substring(searchPos, breakPos).trim(); - request.emit('response', Message.constructBody(line, true)); - searchPos = breakPos + 1; - } - if (searchPos) { - stdout = stdout.substring(searchPos); - } - }); - }) - .catch((err) => - request.emit( - 'cancel', - Message.constructBody( - JSON.stringify({ - returnValue: false, - errorCode: -1, - errorText: `Unable to exec luna-send-pub: ${err}`, - }), - false, - ), - ), - ); - return request; - } - - /** - * Send a message via luna-send-pub and get response - * @param {string} uri - * @param {string} payload - * @returns Promise to response message - */ - async call(uri, payload) { - const ssh = new SSH(); - const config = await this._getSshConfig(); - await ssh.connect(config); - const { stdout } = await ssh.exec('luna-send-pub', ['-n', '1', uri, JSON.stringify(payload)]); - return Message.constructBody(stdout.trim(), false); - } - - querySmNduid() { - return new Promise((resolve, reject) => { - this.service.call('luna://com.webos.service.sm/deviceid/getIDs', { idType: ['NDUID'] }, (message) => { - if (!message.payload.returnValue) { - reject(new Error('Failed to call getIDs')); - return; - } - const id = message.payload.idList.find((item) => item.idType === 'NDUID')?.idValue; - if (!id) { - reject(new Error('Failed to find NDUID')); - } else { - resolve(id); - } - }); - }); - } - - static async queryNyxNduid() { - const raw = (await asyncReadFile('/var/run/nyx/device_info.json')).toString(); - - const start = raw.indexOf('{'); - const { nduid } = JSON.parse(raw.slice(start, raw.indexOf('}', start))); - if (!nduid) { - throw new Error('Failed to get NDUID from Nyx'); - } - return nduid; - } - - async _getSshConfig() { - if (!this.service.call) { - const conf = this.service; - // Assume this is a static configuration for testing - return { - host: conf.host, - port: conf.port, - username: conf.username, - privateKey: readFileSync(conf.privateKeyPath), - passphrase: conf.passphrase, - }; - } - - let nduId; - - try { - nduId = await this.querySmNduid(); - } catch { - nduId = await Handle.queryNyxNduid(); - } - - return { - host: '127.0.0.1', - port: 9922, - username: 'prisoner', - privateKey: readFileSync('/var/luna/preferences/webos_rsa'), - passphrase: nduId.slice(0, 6).toUpperCase(), - }; - } -}