1
0
mirror of https://github.com/Ylianst/MeshAgent synced 2025-12-16 08:13:30 +00:00

Updated linux-dbus and lock/unlock detection for better support across different linux distros

This commit is contained in:
Bryan Roe
2020-03-13 22:29:29 -07:00
parent 8657342978
commit b42429a368
3 changed files with 298 additions and 270 deletions

File diff suppressed because one or more lines are too long

View File

@@ -18,111 +18,90 @@ try { Object.defineProperty(Array.prototype, "peek", { value: function () { retu
function dbus(address, uid)
function dbus(address, uid, env)
{
console.log(address, uid, env);
this._ObjectID = 'linux-dbus';
require('events').EventEmitter.call(this, true)
.createEvent('signal');
Object.defineProperty(this, "uid", { value: uid });
this._child = require('child_process').execFile("/bin/sh", ["sh"], { type: require('child_process').SpawnTypes.TERM, uid: uid == null ? -1 : uid });
//this._child = require('child_process').execFile("/bin/sh", ["sh"], { type: require('child_process').SpawnTypes.TERM, uid: uid == null ? -1 : uid });
this._child = require('child_process').execFile("/bin/sh", ["sh"], { env: env, uid: uid == null ? -1 : uid });
this._child.stdin.write('dbus-monitor --session "type=\'signal\', interface=\'' + address + '\'" | ( while read X; do echo "$X"; done )\n');
this._child.stderr.on('data', function (c) { });
this._child.stdout.dbus = this;
this._child.stdout._str = '';
this._child.stdout._pending = [];
this._child.on('exit', function () { });
this._child.stdout._processPending = function _processPending()
{
//console.log(JSON.stringify(this._pending, null, 1));
this._pendingTimeout = null;
var sig = {};
var tmp, tmp2;
var info = this._pending[0].split(';');
for (i = 1; i < info.length; ++i)
{
var info2 = info[i].split('=');
sig[info2[0].trim()] = info2[1].trim();
}
for (i = 1; i < this._pending.length; ++i)
{
if (this._pending[i].startsWith('string '))
{
sig['value'] = this._pending[i].split('"')[1];
}
else if (this._pending[i].startsWith('boolean '))
{
sig['value'] = JSON.parse(this._pending[i].split(' ')[1]);
}
if (this._pending[i].startsWith('array '))
{
sig['data'] = [];
for (i = i + 1; i < this._pending.length; ++i)
{
if (this._pending[i].startsWith('string '))
{
tmp = this._pending[i].split('"')[1].split('=');
tmp2 = {};
tmp2[tmp[0].trim()] = tmp[1].trim();
sig['data'].push(tmp2);
}
}
break;
}
}
this._pending = [];
setImmediate(function (e, s)
{
e.dbus.emit('signal', s);
}, this, sig);
};
this._child.stdout.on('data', function (chunk)
{
// Parse DBUS Data
if (!this.ready) { this.ready = true; return; }
if (this._pendingTimeout) { clearTimeout(this._pendingTimeout); this._pendingTimeout = null; }
//console.log('=>' + chunk.toString() + '<=');
var lines = [];
var tokens = chunk.toString().split('\r\n');
for (var i in tokens)
var i;
var tokens = chunk.toString().split('\n');
for (i in tokens)
{
if (tokens[i] == '')
if (tokens[i].startsWith('signal '))
{
// End of record
this.dbus.preParseRecords(lines);
lines = [];
}
else
{
lines.push(tokens[i]);
if (this._pending.length > 0) { this._processPending(); }
}
this._pending.push(tokens[i]);
}
if (this._pending.length > 0)
{
this._pendingTimeout = setTimeout(function (self) { self._processPending(); }, 500, this);
}
});
this.preParseRecords = function (lines)
{
var record = [];
for (var i in lines)
{
if(lines[i].startsWith('signal '))
{
if(record.length>0)
{
this.parseRecords(record);
}
record = [];
}
record.push(lines[i]);
}
if (record.length > 0)
{
this.parseRecords(record);
}
}
this.parseRecords = function (lines)
{
if (lines[0].startsWith('signal '))
{
var signal = {};
var sigtokens = lines[0].split(' ');
sigtokens.shift();
for (var i in sigtokens) {
var sigitems = sigtokens[i].split('=');
if (sigitems.length == 2) {
signal[sigitems[0]] = sigitems[1];
}
}
lines.shift();
signal.data = lines;
this.parseSignal(signal);
}
}
this.parseSignal = function(signal)
{
var data = signal.data;
signal.data = [];
for(var i=0; i<data.length; ++i)
{
if (data[i].startsWith('array '))
{
signal.data.push([]);
for(i=i+1; i<data.length; ++i)
{
this.parseSignal2(data[i], signal.data.peek());
}
}
else
{
this.parseSignal2(data[i], signal.data);
}
}
this.emit('signal', signal);
}
this.parseSignal2 = function (inputStr, outArray)
{
if(inputStr.startsWith('string '))
{
outArray.push(JSON.parse(inputStr.slice(7)));
}
else if(inputStr.startsWith('boolean '))
{
outArray.push(JSON.parse(inputStr.slice(8)));
}
}
}
module.exports = dbus;
@@ -135,3 +114,49 @@ module.exports.hasService = function hasService(name)
child.waitExit();
return (child.stdout.str.trim() != '');
};
module.exports.getServices = function getServices()
{
var grep = null;
var options = null;
for (var ax in arguments)
{
if(typeof(arguments[ax])=='string')
{
grep = arguments[ax];
}
if(typeof(arguments[ax])=='object')
{
options = arguments[ax];
}
}
if (grep) { grep = ' | grep "' + grep + '"'; } else { grep = ''; }
var child = require('child_process').execFile('/bin/sh', ['sh'], options);
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
child.stdin.write('dbus-send --session --dest=org.freedesktop.DBus --type=method_call --print-reply /org/freedesktop/DBus org.freedesktop.DBus.ListNames' + grep + '\nexit\n');
child.waitExit();
var ret = [];
var i, tmp;
var tokens = child.stdout.str.trim().split('\n');
for (i = 0; i < tokens.length; ++i)
{
if ((tmp = tokens[i].trim()).startsWith('array '))
{
for (i = i + 1; i < tokens.length; ++i)
{
tmp = tokens[i].trim();
if (tmp.startsWith('string '))
{
ret.push(JSON.parse(tmp.split(' ')[1]));
}
}
}
else if(tmp.startsWith('string '))
{
ret.push(JSON.parse(tmp.split(' ')[1]));
}
}
return (ret);
}

View File

@@ -67,30 +67,6 @@ function UserSessions()
.createEvent('locked')
.createEvent('unlocked');
this.enumerateUsers = function enumerateUsers()
{
var promise = require('promise');
var p = new promise(function (res, rej)
{
this.__resolver = res;
this.__rejector = rej;
});
p.__handler = function __handler(users)
{
p.__resolver(users);
};
try
{
this.Current(p.__handler);
}
catch(e)
{
p.__rejector(e);
}
p.parent = this;
return (p);
}
if (process.platform == 'win32')
{
this._serviceHooked = false;
@@ -434,6 +410,41 @@ function UserSessions()
}
else if(process.platform == 'linux' || process.platform == 'freebsd')
{
this.getUid = function getUid(username)
{
var child = require('child_process').execFile('/bin/sh', ['sh']);
child.stdout.str = '';
child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
child.stdin.write("getent passwd \"" + username + "\" | awk -F: '{print $3}'\nexit\n");
child.waitExit();
var ret = parseInt(child.stdout.str);
if (ret >= 0) { return (ret); }
throw ('username: ' + username + ' NOT FOUND');
};
this.Current = function Current(cb)
{
var child = require('child_process').execFile('/bin/sh', ['sh']);
child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
child.stderr.str = ''; child.stderr.on('data', function (chunk) { this.str += chunk.toString(); });
child.stdin.write("who | tr '\\n' '`' | awk -F'`' '" + '{ printf "{"; for(a=1;a<NF;++a) { n=split($a, tok, " "); printf "%s\\"%s\\": \\"%s\\"", (a>1?",":""), tok[2], tok[1]; } printf "}"; }\'\nexit\n');
child.waitExit();
var ret = JSON.parse(child.stdout.str.trim());
for (var key in ret)
{
ret[key] = { Username: ret[key], SessionId: key, State: 'Active', uid: this.getUid(ret[key]) };
}
Object.defineProperty(ret, 'Active', { value: showActiveOnly(ret) });
if (cb)
{
cb.call(this, ret);
}
}
if (process.platform == 'linux')
{
var dbus = require('linux-dbus');
@@ -444,129 +455,60 @@ function UserSessions()
this.user_session.emit('changed');
});
}
this.Current = function Current(cb) {
var retVal = {};
retVal._ObjectID = 'UserSession'
Object.defineProperty(retVal, '_callback', { value: cb });
Object.defineProperty(retVal, '_child', { value: require('child_process').execFile('/usr/bin/last', ['last', '-f', '/var/run/utmp']) });
retVal._child.Parent = retVal;
retVal._child._txt = '';
retVal._child.on('exit', function (code) {
var lines = this._txt.split('\n');
var sessions = [];
var users = {};
for (var i in lines) {
if (lines[i]) {
var tokens = getTokens(lines[i]);
var s = { Username: tokens[0], SessionId: tokens[1] }
if (tokens[3].includes('still logged in')) {
s.State = 'Active';
}
else {
s.LastActive = tokens[3];
}
sessions.push(s);
}
}
sessions.pop();
var usernames = {};
var promises = [];
for (var i in sessions) {
if (sessions[i].Username != 'reboot') {
users[sessions[i].SessionId] = sessions[i];
if (usernames[sessions[i].Username] == null) {
usernames[sessions[i].Username] = -1;
}
}
}
try {
require('promise');
}
catch (e) {
Object.defineProperty(users, 'Active', { value: showActiveOnly(users) });
if (this.Parent._callback) { this.Parent._callback.call(this.Parent, users); }
return;
}
var promise = require('promise');
for (var n in usernames) {
var p = new promise(function (res, rej) {
this.__username = n;
this.__resolver = res; this.__rejector = rej;
this.__child = require('child_process').execFile('/usr/bin/id', ['id', '-u', n]);
this.__child.promise = this;
this.__child.stdout._txt = '';
this.__child.stdout.on('data', function (chunk) { this._txt += chunk.toString(); });
this.__child.on('exit', function (code) {
try {
parseInt(this.stdout._txt);
}
catch (e) {
this.promise.__rejector('invalid uid');
return;
}
var id = parseInt(this.stdout._txt);
this.promise.__resolver(id);
});
});
promises.push(p);
}
promise.all(promises).then(function (plist) {
// Done
var table = {};
for (var i in plist) {
table[plist[i].__username] = plist[i]._internal.completedArgs[0];
}
for (var i in users) {
users[i].uid = table[users[i].Username];
}
Object.defineProperty(users, 'Active', { value: showActiveOnly(users) });
if (retVal._callback) { retVal._callback.call(retVal, users); }
}, function (reason) {
// Failed
Object.defineProperty(users, 'Active', { value: showActiveOnly(users) });
if (retVal._callback) { retVal._callback.call(retVal, users); }
});
});
retVal._child.stdout.Parent = retVal._child;
retVal._child.stdout.on('data', function (chunk) { this.Parent._txt += chunk.toString(); });
return (retVal);
}
this._recheckLoggedInUsers = function _recheckLoggedInUsers()
{
this.enumerateUsers().then(function (u)
{
if (u.Active.length > 0) {
if (u.Active.length > 0)
{
// There is already a user logged in, so we can monitor DBUS for lock/unlock
if (this.parent._linux_lock_watcher != null && this.parent._linux_lock_watcher.uid != u.Active[0].uid) {
if (this.parent._linux_lock_watcher != null && this.parent._linux_lock_watcher.uid != u.Active[0].uid)
{
delete this.parent._linux_lock_watcher;
}
this.parent._linux_lock_watcher = new dbus(process.env['XDG_CURRENT_DESKTOP'] == 'Unity' ? 'com.ubuntu.Upstart0_6' : 'org.gnome.ScreenSaver', u.Active[0].uid);
this.parent._linux_lock_watcher.user_session = this.parent;
this.parent._linux_lock_watcher.on('signal', function (s) {
var p = this.user_session.enumerateUsers();
p.signalData = s.data[0];
p.then(function (u) {
switch (this.signalData) {
case true:
case 'desktop-lock':
this.parent.emit('locked', u.Active[0]);
break;
case false:
case 'desktop-unlock':
this.parent.emit('unlocked', u.Active[0]);
break;
var env = this.parent.findEnvEntry({ username: u.Active[0].Username, grep: 'dbus-daemon', values: ['DBUS_SESSION_BUS_ADDRESS', 'XDG_CURRENT_DESKTOP'] });
if (Object.keys(env).length == 0) { env = this.parent.findEnvEntry({ username: u.Active[0].Username, grep: 'X', values: ['DBUS_SESSION_BUS_ADDRESS', 'XDG_CURRENT_DESKTOP'] }); }
if (Object.keys(env).length == 0) { env = this.parent.findEnvEntry({ username: u.Active[0].Username, grep: 'dbus-daemon', values: ['DBUS_SESSION_BUS_ADDRESS'] }); }
var service;
switch(env['XDG_CURRENT_DESKTOP'])
{
case 'Unity':
service = 'com.ubuntu.Upstart0_6';
break;
default:
service = require('linux-dbus').getServices('ScreenSaver', { uid: u.Active[0].uid, env: env });
if (service.includes('org.gnome.ScreenSaver'))
{
service = 'org.gnome.ScreenSaver';
}
});
else if (service.includes('org.freedesktop.ScreenSaver'))
{
service = 'org.freedesktop.ScreenSaver';
}
else
{
service = null;
}
break;
}
if (!service) { return; }
this.parent._linux_lock_watcher = new dbus(service, u.Active[0].uid, env);
this.parent._linux_lock_watcher.user_session = this.parent;
this.parent._linux_lock_watcher.on('signal', function (s)
{
switch (s.value)
{
case true:
case 'desktop-lock':
this.user_session.emit('locked');
break;
case false:
case 'desktop-unlock':
this.user_session.emit('unlocked');
break;
}
});
}
else if (this.parent._linux_lock_watcher != null) {
@@ -588,31 +530,8 @@ function UserSessions()
return (ret);
};
this.on('changed', this._recheckLoggedInUsers); // For linux Lock/Unlock monitoring, we need to watch for LogOn/LogOff, and keep track of the UID.
// First step, is to see if there is a user logged in:
this._recheckLoggedInUsers();
}
else
{
this.Current = function Current(cb)
{
var child = require('child_process').execFile('/bin/sh', ['sh']);
child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
child.stderr.str = ''; child.stderr.on('data', function (chunk) { this.str += chunk.toString(); });
child.stdin.write("who | tr '\\n' '`' | awk -F'`' '" + '{ printf "{"; for(a=1;a<NF;++a) { n=split($a, tok, " "); printf "%s\\"%s\\": \\"%s\\"", (a>1?",":""), tok[2], tok[1]; } printf "}"; }\'\nexit\n');
child.waitExit();
var ret = JSON.parse(child.stdout.str.trim());
for (var key in ret)
{
ret[key] = { Username: ret[key], SessionId: key, State: 'Active', uid: this.getUid(ret[key]) };
}
if (cb)
{
cb.call(this, ret);
}
}
}
this.minUid = function minUid()
{
var child = require('child_process').execFile('/bin/sh', ['sh']);
@@ -696,18 +615,7 @@ function UserSessions()
child.waitExit();
return (child.stdout.str.trim());
}
this.getUid = function getUid(username)
{
var child = require('child_process').execFile('/bin/sh', ['sh']);
child.stdout.str = '';
child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
child.stdin.write("getent passwd \"" + username + "\" | awk -F: '{print $3}'\nexit\n");
child.waitExit();
var ret = parseInt(child.stdout.str);
if (ret >= 0) { return (ret); }
throw ('username: ' + username + ' NOT FOUND');
};
this.getUsername = function getUsername(uid)
{
var child = require('child_process').execFile('/bin/sh', ['sh']);
@@ -737,6 +645,73 @@ function UserSessions()
child.waitExit();
return (child.stdout.str.trim());
};
this.getPids = function getPids(options)
{
var grep = '';
switch(typeof(options))
{
default:
throw ('Invalid type specified: ' + typeof (options));
break;
case 'number':
grep = ' | grep "' + this.getUsername(options) + '"';
break;
case 'string':
grep = ' | grep "' + options + '"';
break;
case 'object':
if (options.username) { grep = ' | grep "' + options.username + '"'; }
else if (options.uid != null) { grep = ' | grep "' + this.getUsername(options.uid) + '"'; }
if (options.grep)
{
grep += (' | grep "' + options.grep + '"');
}
break;
}
var child = require('child_process').execFile('/bin/sh', ['sh']);
child.stdout.str = ''; child.stdout.on('data', function(c){this.str += c.toString();});
child.stderr.str = ''; child.stderr.on('data', function(c){this.str += c.toString();});
child.stdin.write('ps -e -o pid -o user -o cmd ' + grep + ' |' + " tr '\n' '`' | awk -F'`' '{ " + 'printf "["; for(i=1;i<NF;++i) { split($i, tok, " "); printf "%s%s",(i!=1?",":""), tok[1]; } printf "]"; }\'\nexit\n');
child.waitExit();
return (JSON.parse(child.stdout.str.trim()));
};
this.findEnvEntry = function findEnvEntry(options)
{
var broke = false;
var ret = {};
var pids = this.getPids(options);
var vals;
var j;
for(var i in pids)
{
broke = false;
ret = {};
vals = this.getEnvFromPid(pids[i]);
for (j in options.values)
{
if(vals[options.values[j]])
{
ret[options.values[j]] = vals[options.values[j]];
}
else
{
broke = true;
break;
}
}
}
if (broke)
{
return ({});
}
else
{
return (ret);
}
};
this.getEnvFromPid = function getEnvFromPid(pid)
{
var ret = {};
@@ -1003,7 +978,35 @@ function UserSessions()
}
}
this.enumerateUsers = function enumerateUsers()
{
var promise = require('promise');
var p = new promise(function (res, rej)
{
this.__resolver = res;
this.__rejector = rej;
});
p.__handler = function __handler(users)
{
p.__resolver(users);
};
try
{
this.Current(p.__handler);
}
catch (e)
{
p.__rejector(e);
}
p.parent = this;
return (p);
}
if(process.platform == 'linux')
{
// First step, is to see if there is a user logged in:
this._recheckLoggedInUsers();
}
}
function showActiveOnly(source)
{