'use strict'; // @ts-check // ================================================================================== // wifi.js // ---------------------------------------------------------------------------------- // Description: System Information - library // for Node.js // Copyright: (c) 2014 - 2021 // Author: Sebastian Hildebrandt // ---------------------------------------------------------------------------------- // License: MIT // ================================================================================== // 9. wifi // ---------------------------------------------------------------------------------- const os = require('os'); const exec = require('child_process').exec; const execSync = require('child_process').execSync; const util = require('./util'); let _platform = process.platform; const _linux = (_platform === 'linux'); const _darwin = (_platform === 'darwin'); const _windows = (_platform === 'win32'); function wifiDBFromQuality(quality) { return (parseFloat(quality) / 2 - 100); } function wifiQualityFromDB(db) { const result = 2 * (parseFloat(db) + 100); return result <= 100 ? result : 100; } function wifiFrequencyFromChannel(channel) { const frequencies = { 1: 2412, 2: 2417, 3: 2422, 4: 2427, 5: 2432, 6: 2437, 7: 2442, 8: 2447, 9: 2452, 10: 2457, 11: 2462, 12: 2467, 13: 2472, 14: 2484, 32: 5160, 34: 5170, 36: 5180, 38: 5190, 40: 5200, 42: 5210, 44: 5220, 46: 5230, 48: 5240, 50: 5250, 52: 5260, 54: 5270, 56: 5280, 58: 5290, 60: 5300, 62: 5310, 64: 5320, 68: 5340, 96: 5480, 100: 5500, 102: 5510, 104: 5520, 106: 5530, 108: 5540, 110: 5550, 112: 5560, 114: 5570, 116: 5580, 118: 5590, 120: 5600, 122: 5610, 124: 5620, 126: 5630, 128: 5640, 132: 5660, 134: 5670, 136: 5680, 138: 5690, 140: 5700, 142: 5710, 144: 5720, 149: 5745, 151: 5755, 153: 5765, 155: 5775, 157: 5785, 159: 5795, 161: 5805, 165: 5825, 169: 5845, 173: 5865, 183: 4915, 184: 4920, 185: 4925, 187: 4935, 188: 4940, 189: 4945, 192: 4960, 196: 4980 }; return {}.hasOwnProperty.call(frequencies, channel) ? frequencies[channel] : null; } function getWifiNetworkAlt(iface) { const result = []; try { let iwlistParts = execSync(`export LC_ALL=C; iwlist ${iface} scan 2>&1; unset LC_ALL`).toString().split(' Cell '); if (iwlistParts[0].indexOf('resource busy') >= 0) { return -1; } if (iwlistParts.length > 1) { iwlistParts.shift(); for (let i = 0; i < iwlistParts.length; i++) { const lines = iwlistParts[i].split('\n'); const channel = util.getValue(lines, 'channel', ':', true); const address = (lines && lines.length && lines[0].indexOf('Address:') >= 0 ? lines[0].split('Address:')[1].trim().toLowerCase() : ''); const mode = util.getValue(lines, 'mode', ':', true); const frequency = util.getValue(lines, 'frequency', ':', true); const qualityString = util.getValue(lines, 'Quality', '=', true); const dbParts = qualityString.toLowerCase().split('signal level='); const db = dbParts.length > 1 ? util.toInt(dbParts[1]) : 0; const quality = db ? wifiQualityFromDB(db) : 0; const ssid = util.getValue(lines, 'essid', ':', true); // security and wpa-flags const isWpa = iwlistParts[i].indexOf(' WPA ') >= 0; const isWpa2 = iwlistParts[i].indexOf('WPA2 ') >= 0; const security = []; if (isWpa) { security.push('WPA'); } if (isWpa2) { security.push('WPA2'); } const wpaFlags = []; let wpaFlag = ''; lines.forEach(function (line) { const l = line.trim().toLowerCase(); if (l.indexOf('group cipher') >= 0) { if (wpaFlag) { wpaFlags.push(wpaFlag); } const parts = l.split(':'); if (parts.length > 1) { wpaFlag = parts[1].trim(); } } if (l.indexOf('pairwise cipher') >= 0) { const parts = l.split(':'); if (parts.length > 1) { if (parts[1].indexOf('tkip')) { wpaFlag = (wpaFlag ? 'TKIP/' + wpaFlag : 'TKIP'); } else if (parts[1].indexOf('ccmp')) { wpaFlag = (wpaFlag ? 'CCMP/' + wpaFlag : 'CCMP'); } else if (parts[1].indexOf('proprietary')) { wpaFlag = (wpaFlag ? 'PROP/' + wpaFlag : 'PROP'); } } } if (l.indexOf('authentication suites') >= 0) { const parts = l.split(':'); if (parts.length > 1) { if (parts[1].indexOf('802.1x')) { wpaFlag = (wpaFlag ? '802.1x/' + wpaFlag : '802.1x'); } else if (parts[1].indexOf('psk')) { wpaFlag = (wpaFlag ? 'PSK/' + wpaFlag : 'PSK'); } } } }); if (wpaFlag) { wpaFlags.push(wpaFlag); } result.push({ ssid, bssid: address, mode, channel: channel ? util.toInt(channel) : null, frequency: frequency ? util.toInt(frequency.replace('.', '')) : null, signalLevel: db, quality, security, wpaFlags, rsnFlags: [] }); } } return result; } catch (e) { return -1; } } function wifiNetworks(callback) { return new Promise((resolve) => { process.nextTick(() => { let result = []; if (_linux) { let cmd = 'nmcli --terse --fields active,ssid,bssid,mode,chan,freq,signal,security,wpa-flags,rsn-flags device wifi list 2>/dev/null'; exec(cmd, { maxBuffer: 1024 * 20000 }, function (error, stdout) { const parts = stdout.toString().split('ACTIVE:'); parts.shift(); parts.forEach(part => { part = 'ACTIVE:' + part; const lines = part.split(os.EOL); const channel = util.getValue(lines, 'CHAN'); const frequency = util.getValue(lines, 'FREQ').toLowerCase().replace('mhz', '').trim(); const security = util.getValue(lines, 'SECURITY').replace('(', '').replace(')', ''); const wpaFlags = util.getValue(lines, 'WPA-FLAGS').replace('(', '').replace(')', ''); const rsnFlags = util.getValue(lines, 'RSN-FLAGS').replace('(', '').replace(')', ''); result.push({ ssid: util.getValue(lines, 'SSID'), bssid: util.getValue(lines, 'BSSID').toLowerCase(), mode: util.getValue(lines, 'MODE'), channel: channel ? parseInt(channel, 10) : null, frequency: frequency ? parseInt(frequency, 10) : null, signalLevel: wifiDBFromQuality(util.getValue(lines, 'SIGNAL')), quality: parseFloat(util.getValue(lines, 'SIGNAL')), security: security && security !== 'none' ? security.split(' ') : [], wpaFlags: wpaFlags && wpaFlags !== 'none' ? wpaFlags.split(' ') : [], rsnFlags: rsnFlags && rsnFlags !== 'none' ? rsnFlags.split(' ') : [] }); }); if (result.length === 0) { try { const iwconfigParts = execSync('export LC_ALL=C; iwconfig 2>/dev/null; unset LC_ALL').toString().split('\n\n'); let iface = ''; for (let i = 0; i < iwconfigParts.length; i++) { if (iwconfigParts[i].indexOf('no wireless') === -1) { iface = iwconfigParts[i].split(' ')[0]; } } if (iface) { const res = getWifiNetworkAlt(iface); if (res === -1) { // try again after 4 secs setTimeout(function (iface) { const res = getWifiNetworkAlt(iface); if (res != -1) { result = res; } if (callback) { callback(result); } resolve(result); }, 4000); } else { result = res; if (callback) { callback(result); } resolve(result); } } } catch (e) { if (callback) { callback(result); } resolve(result); } } else { if (callback) { callback(result); } resolve(result); } }); } else if (_darwin) { let cmd = '/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -s'; exec(cmd, { maxBuffer: 1024 * 20000 }, function (error, stdout) { const lines = stdout.toString().split(os.EOL); if (lines && lines.length > 1) { const parsedhead = util.parseHead(lines[0], 1); if (parsedhead.length >= 7) { lines.shift(); lines.forEach(line => { if (line.trim()) { const channelStr = line.substring(parsedhead[3].from, parsedhead[3].to).trim(); const channel = channelStr ? parseInt(channelStr, 10) : null; const signalLevel = line.substring(parsedhead[2].from, parsedhead[2].to).trim(); const securityAll = line.substring(parsedhead[6].from, 1000).trim().split(' '); let security = []; let wpaFlags = []; securityAll.forEach(securitySingle => { if (securitySingle.indexOf('(') > 0) { const parts = securitySingle.split('('); security.push(parts[0]); wpaFlags = wpaFlags.concat(parts[1].replace(')', '').split(',')); } }); wpaFlags = Array.from(new Set(wpaFlags)); result.push({ ssid: line.substring(parsedhead[0].from, parsedhead[0].to).trim(), bssid: line.substring(parsedhead[1].from, parsedhead[1].to).trim().toLowerCase(), mode: '', channel, frequency: wifiFrequencyFromChannel(channel), signalLevel: signalLevel ? parseInt(signalLevel, 10) : null, quality: wifiQualityFromDB(signalLevel), security, wpaFlags, rsnFlags: [] }); } }); } } if (callback) { callback(result); } resolve(result); }); } else if (_windows) { let cmd = 'chcp 65001 && netsh wlan show networks mode=Bssid'; exec(cmd, util.execOptsWin, function (error, stdout) { const parts = stdout.toString('utf8').split(os.EOL + os.EOL + 'SSID '); parts.shift(); parts.forEach(part => { const lines = part.split(os.EOL); if (lines && lines.length >= 8 && lines[0].indexOf(':') >= 0) { let bssid = lines[4].split(':'); bssid.shift(); bssid = bssid.join(':').trim().toLowerCase(); const channel = lines[7].split(':').pop().trim(); const quality = lines[5].split(':').pop().trim(); result.push({ ssid: lines[0].split(':').pop().trim(), bssid, mode: '', channel: channel ? parseInt(channel, 10) : null, frequency: wifiFrequencyFromChannel(channel), signalLevel: wifiDBFromQuality(quality), quality: quality ? parseInt(quality, 10) : null, security: [lines[2].split(':').pop().trim()], wpaFlags: [lines[3].split(':').pop().trim()], rsnFlags: [] }); } }); if (callback) { callback(result); } resolve(result); }); } else { if (callback) { callback(result); } resolve(result); } }); }); } exports.wifiNetworks = wifiNetworks;