prey/prey-node-client

View on GitHub
lib/agent/actions/wipe/index.js

Summary

Maintainability
C
1 day
Test Coverage
const fs = require('fs');
const path = require('path');
const Emitter = require('events').EventEmitter;
const wipe = require('./wipe');
const common = require('../../common');

const gte = common.helpers.is_greater_or_equal;
const keys = require('../../control-panel/api/keys');

const { join } = path;
// eslint-disable-next-line camelcase
const { os_name } = common;
// eslint-disable-next-line import/no-dynamic-require, camelcase
const osWipe = require(`./${os_name}`);

const logger = common.logger.prefix('wipe');
const { system } = common;
const customDirs = require('../../utils/custom-dirs');

let lastErr;
let directories = [];
let cloud = [];
let emitter;
let wipeProcess; // for storing child instance

exports.node_bin = join(system.paths.current, 'bin', 'node');

if (common.os_name === 'windows') exports.node_bin += '.exe';
/**
 * Validates a list of directories.
 *
 * @param {Array} dirs - The list of directories to be validated.
 * @return {boolean} Returns true if all directories are valid, false otherwise.
 */
const validDirs = (dirs) => {
  const validated = customDirs.validateCustomDirs(dirs, false);
  if (!validated) return false;
  let dirValidated;
  [dirValidated, cloud] = validated;

  cloud = customDirs.get_tasks(cloud);
  directories = customDirs.collect_wipe_paths(cloud).concat(dirValidated.split(','));
  return !(directories.length === 0);
};
/**
 * Generates a list of valid types based on the given hash object.
 *
 * @param {Object} hash - The hash object containing the types to be validated.
 * @return {Array} The list of valid types.
 */
const validTypes = (hash) => {
  const list = [];

  // hash keys should be 'wipe_cookies', 'wipe_passwords', etc

  logger.info('typeof hash');
  logger.info(JSON.stringify(hash));
  logger.info(typeof hash);
  const keysHash = Object.keys((hash));
  if (!Array.isArray(keysHash)) return list;
  keysHash.forEach((key) => {
    const val = hash[key].toString().trim();
    if ((val !== 'false' && val !== 'off' && key.includes('wipe_')) && (val === 'on' || val === 'true' || validDirs(val))) {
      const method = key.replace('wipe_', ''); // just 'cookies'
      if (typeof wipe[method] === 'function') list.push(method);
    }
  });

  return list;
};
/**
 * Starts the wiping process.
 *
 * @param {string} id - The ID of the process.
 * @param {Object} options - The options for the wiping process.
 * @param {string} options.token - The token for authentication.
 * @param {Function} cb - The callback function.
 * @return {void}
 */
// eslint-disable-next-line consistent-return
exports.start = (id, options, cb) => {
  const opts = options || {};
  const token = opts.token || null;
  // const confirm = opts.confirm === 'ireallyknowwhatiamdoing';
  const items = validTypes(opts);

  // if (!confirm)
  //   return cb(new Error('Invalid confirmation string.'))

  if (items.length === 0) { return cb(new Error('Nothing to wipe!')); }

  logger.warn(`WIPING ${items.join(', ')}`);

  let queued = 0;
  let removed = 0;
  const finished = (err, service) => {
    logger.warn(`Error when removing files: ${err}`);
    logger.warn(`Process finished! ${service ? '' : `${removed} dir(s) removed.`}`);

    if (!emitter) return;
    // eslint-disable-next-line consistent-return
    if (service) return emitter.emit('end', id, err);

    // if no files were removed, treat that as an error
    if (!err && removed === 0) {
      emitter.emit('end', id, new Error('No dirs were removed.'));
    } else {
      emitter.emit('end', id, err, { files_removed: removed });
    }
  };
  // runs it within this context, unlike the spawn option
  // eslint-disable-next-line consistent-return
  const queue = (error, method) => {
    queued += 1;
    if (typeof wipe[method] === 'function') {
      wipe[method]((err, removedItems) => {
        if (err) lastErr = err;

        removed += removedItems;
        queued -= 1;
        if (!queued) finished(id, lastErr);
      });
    } else return finished(id, error);
  };

  const compareWinsvcVersion = (callback) => {
    // eslint-disable-next-line global-require
    const sysWin = require('../../../system/windows');
    // eslint-disable-next-line consistent-return
    sysWin.get_winsvc_version((err, serviceVersion) => {
      if (err) return callback(false);

      if (serviceVersion && gte(serviceVersion, '2.0.3')) return callback(true);
      callback(false);
    });
  };

  // run it as another process, using impersonation (to avoid permission errors)
  const spawn = () => {
    const wipeOpts = [
      '-token', opts.token,
      cloud.toString(),
      directories.toString(),
      !!opts.fill_only,
      !!opts.keep_root,
      !!opts.three_pass,
    ];

    // wipe.wipeConfiguration(opts.fill_only, opts.keep_root, opts.three_pass);

    const args = [join(__dirname, 'runner.js')].concat(items).concat(wipeOpts);
    // eslint-disable-next-line consistent-return
    system.spawn_as_admin_user(exports.node_bin, args, (err, child) => {
      if (err) {
        // eslint-disable-next-line camelcase
        if (err.toString().includes('No logged user') && os_name === 'windows') {
          logger.warn('Not logged user found, proceding without impersonation');
          return queue(err, items);
        } return finished(id, err);
      }

      if (typeof child === 'function') { // only for windows
        osWipe.paths.directories = directories;

        logger.info(`${osWipe.paths.directories}`);
        wipe.fetch_dirs(items, directories, cloud, ['-token', token], (_err, dirs) => {
          compareWinsvcVersion((newer) => {
            const optsData = {
              dirs: dirs.dirsToWipe.concat(dirs.dirsToWipeKeep),
              token,
              key: keys.get().device.toString(),
            };

            if (newer) {
              optsData.dirs = dirs.dirsToWipe;
              optsData.dir_keep = dirs.dirsToWipeKeep;
            }
            child('wipe', optsData, (errChild) => {
              if (errChild) lastErr = new Error('Wipe command failed through service');
              finished(id, lastErr);
            });
          });
        });
      } else {
        child.stdout.on('data', (str) => {
          const lines = str.toString().split(/\n/);
          lines.forEach((line) => {
            if (line.toString().match('Removing directory')) {
              logger.warn(line.trim());
              removed += 1;
            } else if (line.toString().match('Error while removing dir')) {
              logger.warn(line.trim());
            } else if (line.trim() !== '') {
              logger.debug(line.trim());
            }
          });
        });

        child.on('exit', (code) => {
          if (code !== 0) { lastErr = new Error('Wipe command failed.'); }

          finished(id, lastErr);
        });

        wipeProcess = child;
      }
    });
  };

  emitter = new Emitter();
  cb(null, emitter);
  if (fs.existsSync(exports.node_bin)) spawn();
  else finished(id, new Error('Node binary not present'));
};
/**
 * Stops the execution of the function.
 *
 * @return {void}
 */
exports.stop = () => {
  if (wipeProcess) wipeProcess.kill();

  emitter = null;
};

exports.validTypes = validTypes;
exports.directories = directories;
exports.cloud = cloud;