users.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. 'use strict';
  2. // @ts-check
  3. // ==================================================================================
  4. // users.js
  5. // ----------------------------------------------------------------------------------
  6. // Description: System Information - library
  7. // for Node.js
  8. // Copyright: (c) 2014 - 2022
  9. // Author: Sebastian Hildebrandt
  10. // ----------------------------------------------------------------------------------
  11. // License: MIT
  12. // ==================================================================================
  13. // 11. Users/Sessions
  14. // ----------------------------------------------------------------------------------
  15. const exec = require('child_process').exec;
  16. const util = require('./util');
  17. let _platform = process.platform;
  18. const _linux = (_platform === 'linux' || _platform === 'android');
  19. const _darwin = (_platform === 'darwin');
  20. const _windows = (_platform === 'win32');
  21. const _freebsd = (_platform === 'freebsd');
  22. const _openbsd = (_platform === 'openbsd');
  23. const _netbsd = (_platform === 'netbsd');
  24. const _sunos = (_platform === 'sunos');
  25. // let _winDateFormat = {
  26. // dateFormat: '',
  27. // dateSeperator: '',
  28. // timeFormat: '',
  29. // timeSeperator: '',
  30. // amDesignator: '',
  31. // pmDesignator: ''
  32. // };
  33. // --------------------------
  34. // array of users online = sessions
  35. // function getWinCulture() {
  36. // return new Promise((resolve) => {
  37. // process.nextTick(() => {
  38. // if (!_winDateFormat.dateFormat) {
  39. // util.powerShell('(get-culture).DateTimeFormat')
  40. // .then(data => {
  41. // let lines = data.toString().split('\r\n');
  42. // _winDateFormat.dateFormat = util.getValue(lines, 'ShortDatePattern', ':');
  43. // _winDateFormat.dateSeperator = util.getValue(lines, 'DateSeparator', ':');
  44. // _winDateFormat.timeFormat = util.getValue(lines, 'ShortTimePattern', ':');
  45. // _winDateFormat.timeSeperator = util.getValue(lines, 'TimeSeparator', ':');
  46. // _winDateFormat.amDesignator = util.getValue(lines, 'AMDesignator', ':');
  47. // _winDateFormat.pmDesignator = util.getValue(lines, 'PMDesignator', ':');
  48. // resolve(_winDateFormat);
  49. // })
  50. // .catch(() => {
  51. // resolve(_winDateFormat);
  52. // });
  53. // } else {
  54. // resolve(_winDateFormat);
  55. // }
  56. // });
  57. // });
  58. // }
  59. function parseUsersLinux(lines, phase) {
  60. let result = [];
  61. let result_who = [];
  62. let result_w = {};
  63. let w_first = true;
  64. let w_header = [];
  65. let w_pos = [];
  66. let who_line = {};
  67. let is_whopart = true;
  68. lines.forEach(function (line) {
  69. if (line === '---') {
  70. is_whopart = false;
  71. } else {
  72. let l = line.replace(/ +/g, ' ').split(' ');
  73. // who part
  74. if (is_whopart) {
  75. result_who.push({
  76. user: l[0],
  77. tty: l[1],
  78. date: l[2],
  79. time: l[3],
  80. ip: (l && l.length > 4) ? l[4].replace(/\(/g, '').replace(/\)/g, '') : ''
  81. });
  82. } else {
  83. // w part
  84. if (w_first) { // header
  85. w_header = l;
  86. w_header.forEach(function (item) {
  87. w_pos.push(line.indexOf(item));
  88. });
  89. w_first = false;
  90. } else {
  91. // split by w_pos
  92. result_w.user = line.substring(w_pos[0], w_pos[1] - 1).trim();
  93. result_w.tty = line.substring(w_pos[1], w_pos[2] - 1).trim();
  94. result_w.ip = line.substring(w_pos[2], w_pos[3] - 1).replace(/\(/g, '').replace(/\)/g, '').trim();
  95. result_w.command = line.substring(w_pos[7], 1000).trim();
  96. // find corresponding 'who' line
  97. who_line = result_who.filter(function (obj) {
  98. return (obj.user.substring(0, 8).trim() === result_w.user && obj.tty === result_w.tty);
  99. });
  100. if (who_line.length === 1) {
  101. result.push({
  102. user: who_line[0].user,
  103. tty: who_line[0].tty,
  104. date: who_line[0].date,
  105. time: who_line[0].time,
  106. ip: who_line[0].ip,
  107. command: result_w.command
  108. });
  109. }
  110. }
  111. }
  112. }
  113. });
  114. if (result.length === 0 && phase === 2) {
  115. return result_who;
  116. } else {
  117. return result;
  118. }
  119. }
  120. function parseUsersDarwin(lines) {
  121. let result = [];
  122. let result_who = [];
  123. let result_w = {};
  124. let who_line = {};
  125. let is_whopart = true;
  126. lines.forEach(function (line) {
  127. if (line === '---') {
  128. is_whopart = false;
  129. } else {
  130. let l = line.replace(/ +/g, ' ').split(' ');
  131. // who part
  132. if (is_whopart) {
  133. result_who.push({
  134. user: l[0],
  135. tty: l[1],
  136. date: ('' + new Date().getFullYear()) + '-' + ('0' + ('JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC'.indexOf(l[2].toUpperCase()) / 3 + 1)).slice(-2) + '-' + ('0' + l[3]).slice(-2),
  137. time: l[4],
  138. });
  139. } else {
  140. // w part
  141. // split by w_pos
  142. result_w.user = l[0];
  143. result_w.tty = l[1];
  144. result_w.ip = (l[2] !== '-') ? l[2] : '';
  145. result_w.command = l.slice(5, 1000).join(' ');
  146. // find corresponding 'who' line
  147. who_line = result_who.filter(function (obj) {
  148. return (obj.user === result_w.user && (obj.tty.substring(3, 1000) === result_w.tty || obj.tty === result_w.tty));
  149. });
  150. if (who_line.length === 1) {
  151. result.push({
  152. user: who_line[0].user,
  153. tty: who_line[0].tty,
  154. date: who_line[0].date,
  155. time: who_line[0].time,
  156. ip: result_w.ip,
  157. command: result_w.command
  158. });
  159. }
  160. }
  161. }
  162. });
  163. return result;
  164. }
  165. function users(callback) {
  166. return new Promise((resolve) => {
  167. process.nextTick(() => {
  168. let result = [];
  169. // linux
  170. if (_linux) {
  171. exec('who --ips; echo "---"; w | tail -n +2', function (error, stdout) {
  172. if (!error) {
  173. // lines / split
  174. let lines = stdout.toString().split('\n');
  175. result = parseUsersLinux(lines, 1);
  176. if (result.length === 0) {
  177. exec('who; echo "---"; w | tail -n +2', function (error, stdout) {
  178. if (!error) {
  179. // lines / split
  180. lines = stdout.toString().split('\n');
  181. result = parseUsersLinux(lines, 2);
  182. }
  183. if (callback) { callback(result); }
  184. resolve(result);
  185. });
  186. } else {
  187. if (callback) { callback(result); }
  188. resolve(result);
  189. }
  190. } else {
  191. if (callback) { callback(result); }
  192. resolve(result);
  193. }
  194. });
  195. }
  196. if (_freebsd || _openbsd || _netbsd) {
  197. exec('who; echo "---"; w -ih', function (error, stdout) {
  198. if (!error) {
  199. // lines / split
  200. let lines = stdout.toString().split('\n');
  201. result = parseUsersDarwin(lines);
  202. }
  203. if (callback) { callback(result); }
  204. resolve(result);
  205. });
  206. }
  207. if (_sunos) {
  208. exec('who; echo "---"; w -h', function (error, stdout) {
  209. if (!error) {
  210. // lines / split
  211. let lines = stdout.toString().split('\n');
  212. result = parseUsersDarwin(lines);
  213. }
  214. if (callback) { callback(result); }
  215. resolve(result);
  216. });
  217. }
  218. if (_darwin) {
  219. exec('who; echo "---"; w -ih', function (error, stdout) {
  220. if (!error) {
  221. // lines / split
  222. let lines = stdout.toString().split('\n');
  223. result = parseUsersDarwin(lines);
  224. }
  225. if (callback) { callback(result); }
  226. resolve(result);
  227. });
  228. }
  229. if (_windows) {
  230. try {
  231. // const workload = [];
  232. // // workload.push(util.powerShell('Get-CimInstance -ClassName Win32_Account | fl *'));
  233. // workload.push(util.powerShell('Get-WmiObject Win32_LogonSession | fl *'));
  234. // workload.push(util.powerShell('Get-WmiObject Win32_LoggedOnUser | fl *'));
  235. // workload.push(util.powerShell('Get-WmiObject Win32_Process -Filter "name=\'explorer.exe\'" | Select @{Name="domain";Expression={$_.GetOwner().Domain}}, @{Name="username";Expression={$_.GetOwner().User}} | fl'));
  236. // Promise.all(
  237. // workload
  238. // ).then(data => {
  239. let cmd = 'Get-WmiObject Win32_LogonSession | select LogonId,StartTime | fl' + '; echo \'#-#-#-#\';';
  240. cmd += 'Get-WmiObject Win32_LoggedOnUser | select antecedent,dependent | fl ' + '; echo \'#-#-#-#\';';
  241. cmd += 'Get-WmiObject Win32_Process -Filter "name=\'explorer.exe\'" | Select @{Name="sessionid";Expression={$_.SessionId}}, @{Name="domain";Expression={$_.GetOwner().Domain}}, @{Name="username";Expression={$_.GetOwner().User}} | fl' + '; echo \'#-#-#-#\';';
  242. cmd += 'query user';
  243. util.powerShell(cmd).then(data => {
  244. // controller + vram
  245. // let accounts = parseWinAccounts(data[0].split(/\n\s*\n/));
  246. if (data) {
  247. data = data.split('#-#-#-#');
  248. let sessions = parseWinSessions((data[0] || '').split(/\n\s*\n/));
  249. let loggedons = parseWinLoggedOn((data[1] || '').split(/\n\s*\n/));
  250. let queryUser = parseWinUsersQuery((data[3] || '').split('\r\n'));
  251. let users = parseWinUsers((data[2] || '').split(/\n\s*\n/), queryUser);
  252. for (let id in loggedons) {
  253. if ({}.hasOwnProperty.call(loggedons, id)) {
  254. loggedons[id].dateTime = {}.hasOwnProperty.call(sessions, id) ? sessions[id] : '';
  255. }
  256. }
  257. users.forEach(user => {
  258. let dateTime = '';
  259. for (let id in loggedons) {
  260. if ({}.hasOwnProperty.call(loggedons, id)) {
  261. if (loggedons[id].user === user.user && (!dateTime || dateTime < loggedons[id].dateTime)) {
  262. dateTime = loggedons[id].dateTime;
  263. }
  264. }
  265. }
  266. result.push({
  267. user: user.user,
  268. tty: user.tty,
  269. date: `${dateTime.substr(0, 4)}-${dateTime.substr(4, 2)}-${dateTime.substr(6, 2)}`,
  270. time: `${dateTime.substr(8, 2)}:${dateTime.substr(10, 2)}`,
  271. ip: '',
  272. command: ''
  273. });
  274. });
  275. }
  276. if (callback) { callback(result); }
  277. resolve(result);
  278. });
  279. // util.powerShell('query user').then(stdout => {
  280. // if (stdout) {
  281. // // lines / split
  282. // let lines = stdout.toString().split('\r\n');
  283. // getWinCulture()
  284. // .then(culture => {
  285. // result = parseUsersWin(lines, culture);
  286. // if (callback) { callback(result); }
  287. // resolve(result);
  288. // });
  289. // } else {
  290. // if (callback) { callback(result); }
  291. // resolve(result);
  292. // }
  293. // });
  294. } catch (e) {
  295. if (callback) { callback(result); }
  296. resolve(result);
  297. }
  298. }
  299. });
  300. });
  301. }
  302. // function parseWinAccounts(accountParts) {
  303. // const accounts = [];
  304. // accountParts.forEach(account => {
  305. // const lines = account.split('\r\n');
  306. // const name = util.getValue(lines, 'name', ':', true);
  307. // const domain = util.getValue(lines, 'domain', ':', true);
  308. // accounts.push(`${domain}\${name}`);
  309. // });
  310. // return accounts;
  311. // }
  312. function parseWinSessions(sessionParts) {
  313. const sessions = {};
  314. sessionParts.forEach(session => {
  315. const lines = session.split('\r\n');
  316. const id = util.getValue(lines, 'LogonId');
  317. const starttime = util.getValue(lines, 'starttime');
  318. if (id) {
  319. sessions[id] = starttime;
  320. }
  321. });
  322. return sessions;
  323. }
  324. function fuzzyMatch(name1, name2) {
  325. name1 = name1.toLowerCase();
  326. name2 = name2.toLowerCase();
  327. let eq = 0;
  328. let len = name1.length;
  329. if (name2.length > len) { len = name2.length; }
  330. for (let i = 0; i < len; i++) {
  331. const c1 = name1[i] || '';
  332. const c2 = name2[i] || '';
  333. if (c1 === c2) { eq++; }
  334. }
  335. return (len > 10 ? eq / len > 0.9 : (len > 0 ? eq / len > 0.8 : false));
  336. }
  337. function parseWinUsers(userParts, userQuery) {
  338. const users = [];
  339. userParts.forEach(user => {
  340. const lines = user.split('\r\n');
  341. const domain = util.getValue(lines, 'domain', ':', true);
  342. const username = util.getValue(lines, 'username', ':', true);
  343. const sessionid = util.getValue(lines, 'sessionid', ':', true);
  344. if (username) {
  345. const quser = userQuery.filter(item => fuzzyMatch(item.user, username));
  346. users.push({
  347. domain,
  348. user: username,
  349. tty: quser && quser[0] && quser[0].tty ? quser[0].tty : sessionid
  350. });
  351. }
  352. });
  353. return users;
  354. }
  355. function parseWinLoggedOn(loggedonParts) {
  356. const loggedons = {};
  357. loggedonParts.forEach(loggedon => {
  358. const lines = loggedon.split('\r\n');
  359. const antecendent = util.getValue(lines, 'antecedent', ':', true);
  360. let parts = antecendent.split(',');
  361. const domainParts = parts.length > 1 ? parts[0].split('=') : [];
  362. const nameParts = parts.length > 1 ? parts[1].split('=') : [];
  363. const domain = domainParts.length > 1 ? domainParts[1].replace(/"/g, '') : '';
  364. const name = nameParts.length > 1 ? nameParts[1].replace(/"/g, '') : '';
  365. const dependent = util.getValue(lines, 'dependent', ':', true);
  366. parts = dependent.split('=');
  367. const id = parts.length > 1 ? parts[1].replace(/"/g, '') : '';
  368. if (id) {
  369. loggedons[id] = {
  370. domain,
  371. user: name
  372. };
  373. }
  374. });
  375. return loggedons;
  376. }
  377. function parseWinUsersQuery(lines) {
  378. lines = lines.filter(item => item);
  379. let result = [];
  380. const header = lines[0];
  381. const headerDelimiter = [];
  382. if (header) {
  383. const start = (header[0] === ' ') ? 1 : 0;
  384. headerDelimiter.push(start - 1);
  385. let nextSpace = 0;
  386. for (let i = start + 1; i < header.length; i++) {
  387. if (header[i] === ' ' && ((header[i - 1] === ' ') || (header[i - 1] === '.'))) {
  388. nextSpace = i;
  389. } else {
  390. if (nextSpace) {
  391. headerDelimiter.push(nextSpace);
  392. nextSpace = 0;
  393. }
  394. }
  395. }
  396. for (let i = 1; i < lines.length; i++) {
  397. if (lines[i].trim()) {
  398. const user = lines[i].substring(headerDelimiter[0] + 1, headerDelimiter[1]).trim() || '';
  399. const tty = lines[i].substring(headerDelimiter[1] + 1, headerDelimiter[2] - 2).trim() || '';
  400. // const dateTime = util.parseDateTime(lines[i].substring(headerDelimiter[5] + 1, 2000).trim(), culture) || '';
  401. result.push({
  402. user: user,
  403. tty: tty,
  404. });
  405. }
  406. }
  407. }
  408. return result;
  409. }
  410. exports.users = users;