prey/prey-node-client

View on GitHub
lib/system/index.js

Summary

Maintainability
D
1 day
Test Coverage
const fs = require('fs'); // for fs.existsSync
const os = require('os');
const { join } = require('path');
const async = require('async');
const remember = require('memorize');
const cp = require('child_process');

const os_name = process.platform.replace('darwin', 'mac').replace('win32', 'windows');
const system = require(join(__dirname, os_name));
const { version } = require(join('..', '..', 'package'));

/// ///////////////////////////////////////////////////
// helpers

const clean_string = function (str) {
  return str.replace(/[^A-Za-z0-9\s]/g, '_').trim();
};

const capitalize = function (str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
};

/// ///////////////////////////////////////////////////
// exports, base functions

module.exports = system;
system.os_name = os_name;
system.get_os_version((err, os_version) => {
  if (!err) {
    system.os_release = os_version;
    system.user_agent = `Prey/${version} (Node ${process.version} ${system.os_name[0].toUpperCase()}${system.os_name.slice(1)} ${os_version})`;
  }
});

system.paths = require('./paths');

// bin/scripts for as_logged_user
const node_bin = join(system.paths.package, 'bin', 'node');
const safexec_bin = join(__dirname, 'windows', 'bin', 'safexec.exe');
const runner_script = join(__dirname, 'utils', 'runner.js');

system.get_device_name = function (cb) {
  return os.hostname().replace(/\.local$/, ''); // remove trailing '.local'
};

system.tempfile_path = function (filename) {
  return join(system.paths.temp, filename);
};

system.get_os_info = remember((cb) => {
  let done;
  const data = {};

  data.arch = process.arch == 'x64' ? 'x64' : 'x86';

  async.parallel([
    system.get_os_name,
    system.get_os_version,
  ], (err, results) => {
    if (done) return;

    data.name = results[0] ? capitalize(results[0]) : os_name;
    if (results[1]) data.version = results[1];

    cb(err, data);
    done = true;
  });
});

system.get_lang((lang) => {
  system.lang = lang;
});

system.python_version = null;
system.get_python_version((err, ver) => {
  if (!err && ver) system.python_version = ver;
});

system.get_current_hostname = system.get_current_hostname;

if (system.os_name == 'windows') {
  system.get_os_edition = system.get_os_edition;
  system.get_winsvc_version = system.get_winsvc_version;
  system.get_as_admin = system.get_as_admin;
  system.run_as_admin = system.run_as_admin;
  system.check_service = system.check_service;
}

/// ///////////////////////////////////////////////////
// logger user, running user

system.get_logged_user = (cb) => {
  system.find_logged_user((e, user) => {
    if (e || !user || user.trim() == '') {
      const err = new Error('No logged user detected.');
      if (e) err.message += ` ${e.message}`;
      err.code = 'NO_LOGGED_USER';
      return cb(err);
    }
    cb(null, user);
  });
};

system.get_admin_user = remember((cb) => {
  system.find_admin_user((e, user) => {
    if (e || !user || user.trim() == '') {
      const err = new Error('No admin user detected.');
      if (e) err.message += ` ${e.message}`;
      err.code = 'NO_ADMIN_USER';
      return cb(err);
    }
    cb(null, user);
  });
});

system.get_running_user = function () {
  const s = process.env.LOGNAME
          || process.env.USER
          || process.env.USERNAME || 'System';
  return clean_string(s);
};

const get_user = {
  logged_user: system.get_logged_user,
  admin_user: system.get_admin_user,
};

function get(type, cb) {
  get_user[type]((err, user) => cb(err, user));
}

/// ///////////////////////////////////////////////////
// impersonation

system.spawn_as_logged_user = function (command, args, opts, cb) {
  as('logged_user', 'spawn', command, args, opts, cb);
};

system.get_as_admin_user = function (what, cb) {
  system.check_service(what, (err, data) => {
    if (err) return cb(err);

    system.get_as_admin(data, cb);
  });
};

system.spawn_as_admin_user = function (command, args, opts, cb) {
  const options = {
    command, args, opts, cb,
  };

  if (system.os_name == 'windows') {
    system.check_service(options, (err, data) => { // An error means the new service isn't available
      if (err) {
        if (options.args && Array.isArray(options.args) && options.args[0].includes('wipe')) // For wipe only when service is not available
        { return as('logged_user', 'spawn', data.command, data.args, data.opts, data.cb); }

        var cb = data.opts;
        return cb();
      }

      var { cb } = data;
      if (typeof data.opts === 'function') cb = data.opts;
      return cb(null, system.run_as_admin);
    });
  } else as('admin_user', 'spawn', command, args, opts, cb);
};

system.run_as_logged_user = function (command, args, opts, cb) {
  as('logged_user', 'exec', command, args, opts, cb);
};

system.kill_as_logged_user = function (pid, cb) {
  var cb = cb || function () { /* boo-hoo */ };
  as('logged_user', 'exec', 'kill', [pid], {}, cb);
};

/**
 * run_as_user options have the same signature as as_user.
 */
system.run_as_user = as_user;

function as(user_type, type, bin, args, opts, cb) {
  if (typeof opts === 'function') {
    var cb = opts;
    var opts = {};
  } else if (!opts) {
    var opts = {};
  }

  get(user_type, (err, user) => {
    if (err) return cb(err);

    const options = {
      user,
      type,
      bin,
      args,
      opts,
    };
    as_user(options, cb);
  });
}

/**
 * as_user runs a command as the specified user.
 * An opts example is as follows:
 * {
 *  user: 'someuser'
 *  type: 'exec' | 'spawn'
 *  bin: '/usr/sbin/screencapture'
 *  args: '/tmp/dest.jpg'
 *  opts: {}
 * }
 */
function as_user(options, cb) {
  const { user } = options;
  const { bin } = options;
  const { type } = options;
  const { opts } = options;
  const { args } = options;
  let finished = false;
  let runner = options.bin;
  let command = options.args;

  if (!(user === system.get_running_user())) {
    if (os_name == 'windows') {
      runner = safexec_bin;
      if (type == 'exec') runner = `"${runner}"`;
      command = [`"${bin}"`].concat(args.map((x) => `"${x}"`));
    } else {
      runner = runner_script;
      command = [user, bin].concat(args);

      if (fs.existsSync(node_bin)) {
        command = [runner].concat(command);
        runner = node_bin;
      }
    }
  }

  if (!opts.env) opts.env = process.env;

  if (process.platform == 'linux' && !opts.env.DISPLAY) {
    opts.env.DISPLAY = ':0'; // ensure active display is used
  }

  if (type == 'exec') {
    const cmd = `${runner} ${command ? command.join(' ') : ''}`;
    var child = cp.exec(cmd, opts, cb);
    child.impersonating = user;
    return child;
  }

  const done = function (e) {
    if (finished) return;

    finished = true;
    cb(e, child);
  };

  // ok, so they want spawn mode. let's fire up the command.
  var child = cp.spawn(runner, command, opts);
  child.impersonating = user;

  // set a listener on error, so if it fails the app doesn't crash!
  child.on('error', (err) => {
    if (err.code == 'ENOENT') { err.message = `ENOENT - Executable not found: ${runner}`; }

    done(err);
  });

  process.nextTick(done);
}