prey/prey-node-client

View on GitHub
lib/agent/providers/network/windows.js

Summary

Maintainability
C
7 hrs
Test Coverage
/* eslint-disable linebreak-style */

/// ///////////////////////////////////////
// Prey JS Network Module Windows Functions
// (c) 2011 - Fork Ltd.
// by Tomas Pollak - http://forkhq.com
// GPLv3 Licensed
/// ///////////////////////////////////////

const wmic = require('wmic');
const { exec } = require('child_process');
const os = require('os');
const si = require('systeminformation');
const common = require('../../common');

const release = parseFloat(os.release());

/**
 * Callsback a list of wireless adapter names.
 * */
exports.get_wireless_interfaces_list = function(callback) {
  var query = 'nic where "Name like \'%Wireless%\'" get NetConnectionID';
 
    wmic.run(query, function(err, o) {
      if (err) {
        si.networkInterfaceDefault((defaultNetwork) => {
          callback(null, defaultNetwork);
        }) 
      }
      else{
        var list = o.split("\n").splice(1).map(function(n) { return n.trim(); });
        callback(null, list);
      }
    }); 
};

/**
 * Returns the MAC address of the active access point.
 * */
exports.get_active_access_point_mac = (callback) => {
  if (release >= 6.0) {
    // eslint-disable-next-line consistent-return
    exec('netsh wlan show interfaces', (err, stdout) => {
      if (err) return callback(err);

      const bssid = stdout.toString().match(/BSSID\s+:\s?(.+)/);
      if (bssid) {
        callback(null, bssid[1]);
      } else {
        callback(null, '');
      }
    });
  } else {
    callback(new Error('TODO!'));
  }
};

exports.get_active_access_point = (callback) => {
  if (release >= 6.0) {
    // eslint-disable-next-line consistent-return
    exec('netsh wlan show interfaces', { timeout: 5000 }, (error, stdout) => {
      if (error) return callback(error);
      if (!stdout || stdout === '') return callback(new Error('the command response is empty'));
      if (!stdout.includes('SSID')) return callback(new Error('Wifi connection unavailable'));
      try {
        const info = stdout.split('\n');
        const ssid = escape(stdout.toString().match(/SSID\s+:\s?(.+)/)[1]);
        const mac_address = stdout.toString().match(/BSSID\s+:\s?(.+)/)[1];
        const ss = info[18].toString().match(/\s+:\s?(.+)/)[1].trim();

        // Verify info from command output
        if (ss.slice(-1) === '%') {
          const signal_strength = parseInt(ss, 10) - 100;
          const channel = parseInt(info[15].toString().match(/\s+:\s?(.+)/)[1], 10);
          const security = info[12].toString().match(/\s+:\s?(.+)/)[1];

          const ap = {
            ssid, mac_address, signal_strength, channel, security,
          };
          callback(null, ap);

        // When the data isn't consistent we're using the old method
        } else {
          // eslint-disable-next-line consistent-return
          exports.get_access_points_list((err, aps) => {
            if (err) return callback(err);

            const paddedMac = mac_address.toString().trim().replace(/(^|:)(?=[0-9a-fA-F](?::|$))/g, '$10');

            const apsFiltered = aps.filter((ap) => ap.mac_address === paddedMac);

            if (apsFiltered.length > 0) {
              callback(null, apsFiltered[0]);
            } else callback(new Error(`Could not find matching access point for ${paddedMac}`));
          });
        }
      } catch (e) {
        callback(new Error('Unable to get current ap.'));
      }
    });
  } else {
    callback(new Error('TODO!'));
  }
};

/// //////////////////////////////////////////////////////////////
// access points list fetcher and parser
/// //////////////////////////////////////////////////////////////

/**
 * Gets access points list
 * @param {String} wifi_device - should return something like
 * { ssid:"",security:true,quality:23,signal_strength:54,noise_level:24}
 *
 * autowc actually returns {mac_address,ssid,signal_strength,channel,signal_to_noise}
 * this function converts
 * */

exports.get_access_points_list = (callback) => {
  let cmd;
  let parser;
  let list = [];

  // eslint-disable-next-line consistent-return
  const done = (err) => {
    if (err || list.length === 0) {
      if (!err) return callback(new Error('No access points found.'));
      if (err.code === 10) return callback('No Wi-Fi adapter found');
      return callback(err);
    }
    return callback(null, list);
  };

  if (release <= 5.2) {
    cmd = 'autowcxp -list';
    parser = 'autowc';
  } else {
    cmd = 'netsh wlan show all';
    parser = 'netsh';
  }

  exec('chcp 65001', () => {
    common.system.scan_networks(() => {
      // eslint-disable-next-line consistent-return
      exec(cmd, { timeout: 5000 }, (err, out) => {
        if (err) return done(err);
        list = exports[`parse_access_points_list_${parser}`](out);
        done();
      });
    });
  });
};

exports.parse_access_points_list_autowc = (out) => {
  let arr = [];
  try {
    arr = JSON.parse(`[${out}]`);
  } catch (e) {
    return arr;
  }

  if (arr.length === 0) return [];

  return arr.map((o) => ({
    ssid: o.ssid.replace(/[^\w :'-]/g, ''),
    // security     : null, // don't have this data
    // quality      : null,
    signal_strength: o.signal_strength,
    noise_level: o.signal_to_noise,
  }));
};

/* example output:
SSID 1 : SomewhereWiFi
    Network type            : Infrastructure
    Authentication          : Open
    Encryption              : None
    BSSID 1                 : aa:bb:cc:11:22:33
         Signal             : 38%
         Radio type         : 802.11n
         Channel            : 6
         Basic rates (Mbps) : 1 2 5.5 11
         Other rates (Mbps) : 6 9 12 18 24 36 48 54
    BSSID 2                 : 33:22:11:bb:cc:dd
         Signal             : 40%
         Radio type         : 802.11n
         Channel            : 4
         Basic rates (Mbps) : 1 2 5.5 11
         Other rates (Mbps) : 6 9 12 18 24 36 48 54
*/

exports.parse_access_points_list_netsh = (out) => {
  // eslint-disable-next-line prefer-const
  let list = [];
  const blocks = out.split(/\nSSID \d{1,2}\s:/);

  if (!blocks) return [];

  const getValues = (str) => {
    // eslint-disable-next-line prefer-const
    let res = {};
    let idx = 0;
    str.split('\n').forEach((line) => {
      if (line.toString().trim() === '') return;

      const split = line.split(': ');
      const key = idx.toString();
      const val = split[1] ? split[1].trim() : null;

      if (key) res[key] = val;
      idx += 1;
    });
    return res;
  };

  const SSID_KEYS = {
    SSID: '0',
    AUTH: '2',
  };

  const BSSID_KEYS = {
    BSSID: '0',
    SIGNAL: '1',
    CHANNEL: '3',
  };

  const buildAp = (base, router) => {
    const obj = {
      ssid: (!base[SSID_KEYS.SSID] || base[SSID_KEYS.SSID].toString().trim() === '') ? '(Unknown)' : base['0'],
      security: base[SSID_KEYS.AUTH] !== 'Open' ? base[SSID_KEYS.AUTH] : null,
      mac_address: router[BSSID_KEYS.BSSID],
    };

    // signal is shown as '94%', so we need to substract 100 to get a consistent behaviour with
    // OSX and Linux's signal_strength integer
    // eslint-disable-next-line max-len
    if (router[BSSID_KEYS.SIGNAL]) obj.signal_strength = parseInt(router[BSSID_KEYS.SIGNAL], 10) - 100;

    if (router[BSSID_KEYS.CHANNEL])obj.channel = parseInt(router[BSSID_KEYS.CHANNEL], 10);

    return obj;
  };

  blocks.forEach((block, i) => {
    if (i === 0) return; // first block contains data about the interface and card

    // netsh groups access points by BSSID, so we need to separate each
    // SSID block into the BSSID it contains and select one of them
    const routers = block.split(/[BSSID BSSIDD] \d/);

    // the first block will contain shared information: SSID, auth, encryption
    // so parse those values first. insert the SSID part that was removed from line one
    const shared = `SSID : ${routers.shift()}`;
    const main = getValues(shared);

    const routersMapped = routers.map((routerData) => {
      const values = getValues(`BSSID${routerData}`);

      if (!values[BSSID_KEYS.BSSID] || values[BSSID_KEYS.BSSID].toString().trim() === '') return;

      // eslint-disable-next-line consistent-return
      return values;
    }).filter((el) => el); // remove invalid entries

    // console.log(main['SSID'] + ' has ' + routers.length + ' routers.');
    routersMapped.forEach((router) => {
      list.push(buildAp(main, router));
    });
  });

  return list;
};

exports.isWifiPermissionActive = (callback) => {
  callback();
};