diff --git a/lib/wifi.js b/lib/wifi.js index cf85081..183677b 100644 --- a/lib/wifi.js +++ b/lib/wifi.js @@ -1,709 +1,716 @@ -'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; -} - -const _wifi_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 -}; - -function wifiFrequencyFromChannel(channel) { - return {}.hasOwnProperty.call(_wifi_frequencies, channel) ? _wifi_frequencies[channel] : null; -} - -function wifiChannelFromFrequencs(frequency) { - let channel = 0; - for (let key in _wifi_frequencies) { - if ({}.hasOwnProperty.call(_wifi_frequencies, key)) { - if (_wifi_frequencies[key] === frequency) { channel = util.toInt(key); } - } - } - return channel; -} - -function ifaceListLinux() { - const result = []; - const cmd = 'iw dev'; - try { - const all = execSync(cmd).toString().split('\n').map(line => line.trim()).join('\n'); - const parts = all.split('\nInterface '); - parts.shift(); - parts.forEach(ifaceDetails => { - const lines = ifaceDetails.split('\n'); - const iface = lines[0]; - const id = util.toInt(util.getValue(lines, 'ifindex', ' ')); - const mac = util.getValue(lines, 'addr', ' '); - const channel = util.toInt(util.getValue(lines, 'channel', ' ')); - result.push({ - id, - iface, - mac, - channel - }); - }); - return result; - } catch (e) { - return []; - } -} - -function nmiDeviceLinux(iface) { - const cmd = `nmcli -t -f general,wifi-properties,capabilities,ip4,ip6 device show ${iface} 2>/dev/null`; - try { - const lines = execSync(cmd).toString().split('\n'); - const ssid = util.getValue(lines, 'GENERAL.CONNECTION'); - return { - iface, - type: util.getValue(lines, 'GENERAL.TYPE'), - vendor: util.getValue(lines, 'GENERAL.VENDOR'), - product: util.getValue(lines, 'GENERAL.PRODUCT'), - mac: util.getValue(lines, 'GENERAL.HWADDR').toLowerCase(), - ssid: ssid !== '--' ? ssid : null - }; - } catch (e) { - return {}; - } -} - -function nmiConnectionLinux(ssid) { - const cmd = `nmcli -t --show-secrets connection show ${ssid} 2>/dev/null`; - try { - const lines = execSync(cmd).toString().split('\n'); - const bssid = util.getValue(lines, '802-11-wireless.seen-bssids').toLowerCase(); - return { - ssid: ssid !== '--' ? ssid : null, - uuid: util.getValue(lines, 'connection.uuid'), - type: util.getValue(lines, 'connection.type'), - autoconnect: util.getValue(lines, 'connection.autoconnect') === 'yes', - security: util.getValue(lines, '802-11-wireless-security.key-mgmt'), - bssid: bssid !== '--' ? bssid : null - }; - } catch (e) { - return {}; - } -} - -function wpaConnectionLinux(iface) { - const cmd = `wpa_cli -i ${iface} status 2>&1`; - try { - const lines = execSync(cmd).toString().split('\n'); - const freq = util.toInt(util.getValue(lines, 'freq', '=')); - return { - ssid: util.getValue(lines, 'ssid', '='), - uuid: util.getValue(lines, 'uuid', '='), - security: util.getValue(lines, 'key_mgmt', '='), - freq, - channel: wifiChannelFromFrequencs(freq), - bssid: util.getValue(lines, 'bssid', '=').toLowerCase() - }; - } catch (e) { - return {}; - } -} - -function getWifiNetworkListNmi() { - const result = []; - const cmd = 'nmcli -t -m multiline --fields active,ssid,bssid,mode,chan,freq,signal,security,wpa-flags,rsn-flags device wifi list 2>/dev/null'; - try { - const stdout = execSync(cmd, { maxBuffer: 1024 * 20000 }); - 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(' ') : [] - }); - }); - return result; - } catch (e) { - return []; - } -} - -function getWifiNetworkListIw(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().toUpperCase(); - } - } - 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) { - result = getWifiNetworkListNmi(); - 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 && iwconfigParts[i].trim() !== '') { - iface = iwconfigParts[i].split(' ')[0]; - } - } - if (iface) { - const res = getWifiNetworkListIw(iface); - if (res === -1) { - // try again after 4 secs - setTimeout(function (iface) { - const res = getWifiNetworkListIw(iface); - if (res != -1) { result = res; } - if (callback) { - callback(result); - } - resolve(result); - }, 4000); - } else { - result = res; - if (callback) { - callback(result); - } - resolve(result); - } - } else { - 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 ssidParts = stdout.toString('utf8').split(os.EOL + os.EOL + 'SSID '); - ssidParts.shift(); - - ssidParts.forEach(ssidPart => { - const ssidLines = ssidPart.split(os.EOL); - if (ssidLines && ssidLines.length >= 8 && ssidLines[0].indexOf(':') >= 0) { - const bssidsParts = ssidPart.split('BSSID '); - bssidsParts.shift(); - - bssidsParts.forEach((bssidPart) => { - const bssidLines = bssidPart.split(os.EOL); - const bssidLine = bssidLines[0].split(':'); - bssidLine.shift(); - const bssid = bssidLine.join(':').trim().toLowerCase(); - const channel = bssidLines[3].split(':').pop().trim(); - const quality = bssidLines[1].split(':').pop().trim(); - - result.push({ - ssid: ssidLines[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: [ssidLines[2].split(':').pop().trim()], - wpaFlags: [ssidLines[3].split(':').pop().trim()], - rsnFlags: [] - }); - }); - } - }); - - if (callback) { - callback(result); - } - resolve(result); - }); - } else { - if (callback) { - callback(result); - } - resolve(result); - } - }); - }); -} - -exports.wifiNetworks = wifiNetworks; - -function getVendor(model) { - model = model.toLowerCase(); - let result = ''; - if (model.indexOf('intel') >= 0) { result = 'Intel'; } - else if (model.indexOf('realtek') >= 0) { result = 'Realtek'; } - else if (model.indexOf('qualcom') >= 0) { result = 'Qualcom'; } - else if (model.indexOf('broadcom') >= 0) { result = 'Broadcom'; } - else if (model.indexOf('cavium') >= 0) { result = 'Cavium'; } - else if (model.indexOf('cisco') >= 0) { result = 'Cisco'; } - else if (model.indexOf('marvel') >= 0) { result = 'Marvel'; } - else if (model.indexOf('zyxel') >= 0) { result = 'Zyxel'; } - else if (model.indexOf('melanox') >= 0) { result = 'Melanox'; } - else if (model.indexOf('d-link') >= 0) { result = 'D-Link'; } - else if (model.indexOf('tp-link') >= 0) { result = 'TP-Link'; } - else if (model.indexOf('asus') >= 0) { result = 'Asus'; } - else if (model.indexOf('linksys') >= 0) { result = 'Linksys'; } - return result; -} - -function wifiConnections(callback) { - - return new Promise((resolve) => { - process.nextTick(() => { - const result = []; - - if (_linux) { - const ifaces = ifaceListLinux(); - const networkList = getWifiNetworkListNmi(); - ifaces.forEach(ifaceDetail => { - const nmiDetails = nmiDeviceLinux(ifaceDetail.iface); - const wpaDetails = wpaConnectionLinux(ifaceDetail.iface); - const ssid = nmiDetails.ssid || wpaDetails.ssid; - const network = networkList.filter(nw => nw.ssid === ssid); - const nmiConnection = nmiConnectionLinux(ssid); - const channel = network && network.length && network[0].channel ? network[0].channel : (wpaDetails.channel ? wpaDetails.channel : null); - const bssid = network && network.length && network[0].bssid ? network[0].bssid : (wpaDetails.bssid ? wpaDetails.bssid : null); - if (ssid && bssid) { - result.push({ - id: ifaceDetail.id, - iface: ifaceDetail.iface, - model: nmiDetails.product, - ssid, - bssid: network && network.length && network[0].bssid ? network[0].bssid : (wpaDetails.bssid ? wpaDetails.bssid : null), - channel, - frequency: channel ? wifiFrequencyFromChannel(channel) : null, - type: nmiConnection.type ? nmiConnection.type : '802.11', - security: nmiConnection.security ? nmiConnection.security : (wpaDetails.security ? wpaDetails.security : null), - signalLevel: network && network.length && network[0].signalLevel ? network[0].signalLevel : null, - txRate: null - }); - } - }); - if (callback) { - callback(result); - } - resolve(result); - } else if (_darwin) { - let cmd = 'system_profiler SPNetworkDataType'; - exec(cmd, function (error, stdout) { - const parts1 = stdout.toString().split('\n\n Wi-Fi:\n\n'); - if (parts1.length > 1) { - const lines = parts1[1].split('\n\n')[0].split('\n'); - const iface = util.getValue(lines, 'BSD Device Name', ':', true); - const model = util.getValue(lines, 'hardware', ':', true); - cmd = '/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I'; - exec(cmd, function (error, stdout) { - const lines2 = stdout.toString().split('\n'); - if (lines.length > 10) { - const ssid = util.getValue(lines2, 'ssid', ':', true); - const bssid = util.getValue(lines2, 'bssid', ':', true); - const security = util.getValue(lines2, 'link auth', ':', true); - const txRate = util.getValue(lines2, 'lastTxRate', ':', true); - const channel = util.getValue(lines2, 'channel', ':', true).split(',')[0]; - const type = '802.11'; - const rssi = util.toInt(util.getValue(lines2, 'agrCtlRSSI', ':', true)); - const noise = util.toInt(util.getValue(lines2, 'agrCtlNoise', ':', true)); - const signalLevel = rssi - noise; - // const signal = wifiQualityFromDB(signalLevel); - if (ssid && bssid) { - result.push({ - id: 'Wi-Fi', - iface, - model, - ssid, - bssid, - channel: util.toInt(channel), - frequency: channel ? wifiFrequencyFromChannel(channel) : null, - type, - security, - signalLevel, - txRate - }); - - } - } - if (callback) { - callback(result); - } - resolve(result); - }); - } - }); - } else if (_windows) { - let cmd = 'netsh wlan show interfaces'; - exec(cmd, util.execOptsWin, function (error, stdout) { - const parts = stdout.toString().split(': \r\n\r\n'); - parts.shift(); - parts.forEach(part => { - const lines = part.split('\r\n'); - if (lines.length >= 5) { - const iface = lines[0].indexOf(':') >= 0 ? lines[0].split(':')[1].trim() : ''; - const model = lines[1].indexOf(':') >= 0 ? lines[1].split(':')[1].trim() : ''; - const id = lines[2].indexOf(':') >= 0 ? lines[2].split(':')[1].trim() : ''; - const ssid = util.getValue(lines, 'SSID', ':', true); - const bssid = util.getValue(lines, 'BSSID', ':', true); - const signalLevel = util.getValue(lines, 'Signal', ':', true); - const type = util.getValue(lines, 'Radio type', ':', true) || util.getValue(lines, 'Type de radio', ':', true) || util.getValue(lines, 'Funktyp', ':', true) || null; - const security = util.getValue(lines, 'authentication', ':', true) || util.getValue(lines, 'Authentification', ':', true) || util.getValue(lines, 'Authentifizierung', ':', true) || null; - const channel = util.getValue(lines, 'Channel', ':', true) || util.getValue(lines, 'Canal', ':', true) || util.getValue(lines, 'Kanal', ':', true) || null; - const txRate = util.getValue(lines, 'Transmit rate (mbps)', ':', true) || util.getValue(lines, 'Transmission (mbit/s)', ':', true) || util.getValue(lines, 'Empfangsrate (MBit/s)', ':', true) || null; - if (model && id && ssid && bssid) { - result.push({ - id, - iface, - model, - ssid, - bssid, - channel: util.toInt(channel), - frequency: channel ? wifiFrequencyFromChannel(channel) : null, - type, - security, - signalLevel, - txRate: util.toInt(txRate) || null - }); - } - } - }); - if (callback) { - callback(result); - } - resolve(result); - }); - } else { - if (callback) { - callback(result); - } - resolve(result); - } - }); - }); -} - -exports.wifiConnections = wifiConnections; - -function wifiInterfaces(callback) { - - return new Promise((resolve) => { - process.nextTick(() => { - const result = []; - - if (_linux) { - const ifaces = ifaceListLinux(); - ifaces.forEach(ifaceDetail => { - const nmiDetails = nmiDeviceLinux(ifaceDetail.iface); - result.push({ - id: ifaceDetail.id, - iface: ifaceDetail.iface, - model: nmiDetails.product ? nmiDetails.product : null, - vendor: nmiDetails.vendor ? nmiDetails.vendor : null, - mac: ifaceDetail.mac, - }); - }); - if (callback) { - callback(result); - } - resolve(result); - } else if (_darwin) { - let cmd = 'system_profiler SPNetworkDataType'; - exec(cmd, function (error, stdout) { - const parts1 = stdout.toString().split('\n\n Wi-Fi:\n\n'); - if (parts1.length > 1) { - const lines = parts1[1].split('\n\n')[0].split('\n'); - const iface = util.getValue(lines, 'BSD Device Name', ':', true); - const mac = util.getValue(lines, 'MAC Address', ':', true); - const model = util.getValue(lines, 'hardware', ':', true); - result.push({ - id: 'Wi-Fi', - iface, - model, - vendor: '', - mac - }); - } - if (callback) { - callback(result); - } - resolve(result); - }); - } else if (_windows) { - let cmd = 'netsh wlan show interfaces'; - exec(cmd, util.execOptsWin, function (error, stdout) { - const parts = stdout.toString().split(': \r\n\r\n'); - parts.shift(); - parts.forEach(part => { - const lines = part.split('\r\n'); - if (lines.length >= 5) { - const iface = lines[0].indexOf(':') >= 0 ? lines[0].split(':')[1].trim() : ''; - const model = lines[1].indexOf(':') >= 0 ? lines[1].split(':')[1].trim() : ''; - const id = lines[2].indexOf(':') >= 0 ? lines[2].split(':')[1].trim() : ''; - const macParts = lines[3].indexOf(':') >= 0 ? lines[3].split(':') : []; - macParts.shift(); - const mac = macParts.join(':').trim(); - const vendor = getVendor(model); - if (iface && model && id && mac) { - result.push({ - id, - iface, - model, - vendor, - mac, - }); - } - } - }); - if (callback) { - callback(result); - } - resolve(result); - }); - } else { - if (callback) { - callback(result); - } - resolve(result); - } - }); - }); -} - -exports.wifiInterfaces = wifiInterfaces; +'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; +} + +const _wifi_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 +}; + +function wifiFrequencyFromChannel(channel) { + return {}.hasOwnProperty.call(_wifi_frequencies, channel) ? _wifi_frequencies[channel] : null; +} + +function wifiChannelFromFrequencs(frequency) { + let channel = 0; + for (let key in _wifi_frequencies) { + if ({}.hasOwnProperty.call(_wifi_frequencies, key)) { + if (_wifi_frequencies[key] === frequency) { channel = util.toInt(key); } + } + } + return channel; +} + +function ifaceListLinux() { + const result = []; + const cmd = 'iw dev'; + try { + const all = execSync(cmd).toString().split('\n').map(line => line.trim()).join('\n'); + const parts = all.split('\nInterface '); + parts.shift(); + parts.forEach(ifaceDetails => { + const lines = ifaceDetails.split('\n'); + const iface = lines[0]; + const id = util.toInt(util.getValue(lines, 'ifindex', ' ')); + const mac = util.getValue(lines, 'addr', ' '); + const channel = util.toInt(util.getValue(lines, 'channel', ' ')); + result.push({ + id, + iface, + mac, + channel + }); + }); + return result; + } catch (e) { + return []; + } +} + +function nmiDeviceLinux(iface) { + const cmd = `nmcli -t -f general,wifi-properties,capabilities,ip4,ip6 device show ${iface} 2>/dev/null`; + try { + const lines = execSync(cmd).toString().split('\n'); + const ssid = util.getValue(lines, 'GENERAL.CONNECTION'); + return { + iface, + type: util.getValue(lines, 'GENERAL.TYPE'), + vendor: util.getValue(lines, 'GENERAL.VENDOR'), + product: util.getValue(lines, 'GENERAL.PRODUCT'), + mac: util.getValue(lines, 'GENERAL.HWADDR').toLowerCase(), + ssid: ssid !== '--' ? ssid : null + }; + } catch (e) { + return {}; + } +} + +function nmiConnectionLinux(ssid) { + const cmd = `nmcli -t --show-secrets connection show ${ssid} 2>/dev/null`; + try { + const lines = execSync(cmd).toString().split('\n'); + const bssid = util.getValue(lines, '802-11-wireless.seen-bssids').toLowerCase(); + return { + ssid: ssid !== '--' ? ssid : null, + uuid: util.getValue(lines, 'connection.uuid'), + type: util.getValue(lines, 'connection.type'), + autoconnect: util.getValue(lines, 'connection.autoconnect') === 'yes', + security: util.getValue(lines, '802-11-wireless-security.key-mgmt'), + bssid: bssid !== '--' ? bssid : null + }; + } catch (e) { + return {}; + } +} + +function wpaConnectionLinux(iface) { + const cmd = `wpa_cli -i ${iface} status 2>&1`; + try { + const lines = execSync(cmd).toString().split('\n'); + const freq = util.toInt(util.getValue(lines, 'freq', '=')); + return { + ssid: util.getValue(lines, 'ssid', '='), + uuid: util.getValue(lines, 'uuid', '='), + security: util.getValue(lines, 'key_mgmt', '='), + freq, + channel: wifiChannelFromFrequencs(freq), + bssid: util.getValue(lines, 'bssid', '=').toLowerCase() + }; + } catch (e) { + return {}; + } +} + +function getWifiNetworkListNmi() { + const result = []; + const cmd = 'nmcli -t -m multiline --fields active,ssid,bssid,mode,chan,freq,signal,security,wpa-flags,rsn-flags device wifi list 2>/dev/null'; + try { + const stdout = execSync(cmd, { maxBuffer: 1024 * 20000 }); + 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(' ') : [] + }); + }); + return result; + } catch (e) { + return []; + } +} + +function getWifiNetworkListIw(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().toUpperCase(); + } + } + 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) { + result = getWifiNetworkListNmi(); + 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 && iwconfigParts[i].trim() !== '') { + iface = iwconfigParts[i].split(' ')[0]; + } + } + if (iface) { + const res = getWifiNetworkListIw(iface); + if (res === -1) { + // try again after 4 secs + setTimeout(function (iface) { + const res = getWifiNetworkListIw(iface); + if (res != -1) { result = res; } + if (callback) { + callback(result); + } + resolve(result); + }, 4000); + } else { + result = res; + if (callback) { + callback(result); + } + resolve(result); + } + } else { + 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 = 'netsh wlan show networks mode=Bssid'; + util.powerShell(cmd).then((stdout, error) => { + const ssidParts = stdout.toString('utf8').split(os.EOL + os.EOL + 'SSID '); + ssidParts.shift(); + + ssidParts.forEach(ssidPart => { + const ssidLines = ssidPart.split(os.EOL); + if (ssidLines && ssidLines.length >= 8 && ssidLines[0].indexOf(':') >= 0) { + const bssidsParts = ssidPart.split(' BSSID'); + bssidsParts.shift(); + + bssidsParts.forEach((bssidPart) => { + const bssidLines = bssidPart.split(os.EOL); + const bssidLine = bssidLines[0].split(':'); + bssidLine.shift(); + const bssid = bssidLine.join(':').trim().toLowerCase(); + const channel = bssidLines[3].split(':').pop().trim(); + const quality = bssidLines[1].split(':').pop().trim(); + + result.push({ + ssid: ssidLines[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: [ssidLines[2].split(':').pop().trim()], + wpaFlags: [ssidLines[3].split(':').pop().trim()], + rsnFlags: [] + }); + }); + } + }); + + if (callback) { + callback(result); + } + resolve(result); + }); + } else { + if (callback) { + callback(result); + } + resolve(result); + } + }); + }); +} + +exports.wifiNetworks = wifiNetworks; + +function getVendor(model) { + model = model.toLowerCase(); + let result = ''; + if (model.indexOf('intel') >= 0) { result = 'Intel'; } + else if (model.indexOf('realtek') >= 0) { result = 'Realtek'; } + else if (model.indexOf('qualcom') >= 0) { result = 'Qualcom'; } + else if (model.indexOf('broadcom') >= 0) { result = 'Broadcom'; } + else if (model.indexOf('cavium') >= 0) { result = 'Cavium'; } + else if (model.indexOf('cisco') >= 0) { result = 'Cisco'; } + else if (model.indexOf('marvel') >= 0) { result = 'Marvel'; } + else if (model.indexOf('zyxel') >= 0) { result = 'Zyxel'; } + else if (model.indexOf('melanox') >= 0) { result = 'Melanox'; } + else if (model.indexOf('d-link') >= 0) { result = 'D-Link'; } + else if (model.indexOf('tp-link') >= 0) { result = 'TP-Link'; } + else if (model.indexOf('asus') >= 0) { result = 'Asus'; } + else if (model.indexOf('linksys') >= 0) { result = 'Linksys'; } + return result; +} + +function wifiConnections(callback) { + + return new Promise((resolve) => { + process.nextTick(() => { + const result = []; + + if (_linux) { + const ifaces = ifaceListLinux(); + const networkList = getWifiNetworkListNmi(); + ifaces.forEach(ifaceDetail => { + const nmiDetails = nmiDeviceLinux(ifaceDetail.iface); + const wpaDetails = wpaConnectionLinux(ifaceDetail.iface); + const ssid = nmiDetails.ssid || wpaDetails.ssid; + const network = networkList.filter(nw => nw.ssid === ssid); + const nmiConnection = nmiConnectionLinux(ssid); + const channel = network && network.length && network[0].channel ? network[0].channel : (wpaDetails.channel ? wpaDetails.channel : null); + const bssid = network && network.length && network[0].bssid ? network[0].bssid : (wpaDetails.bssid ? wpaDetails.bssid : null); + if (ssid && bssid) { + result.push({ + id: ifaceDetail.id, + iface: ifaceDetail.iface, + model: nmiDetails.product, + ssid, + bssid: network && network.length && network[0].bssid ? network[0].bssid : (wpaDetails.bssid ? wpaDetails.bssid : null), + channel, + frequency: channel ? wifiFrequencyFromChannel(channel) : null, + type: nmiConnection.type ? nmiConnection.type : '802.11', + security: nmiConnection.security ? nmiConnection.security : (wpaDetails.security ? wpaDetails.security : null), + signalLevel: network && network.length && network[0].signalLevel ? network[0].signalLevel : null, + txRate: null + }); + } + }); + if (callback) { + callback(result); + } + resolve(result); + } else if (_darwin) { + let cmd = 'system_profiler SPNetworkDataType'; + exec(cmd, function (error, stdout) { + const parts1 = stdout.toString().split('\n\n Wi-Fi:\n\n'); + if (parts1.length > 1) { + const lines = parts1[1].split('\n\n')[0].split('\n'); + const iface = util.getValue(lines, 'BSD Device Name', ':', true); + const model = util.getValue(lines, 'hardware', ':', true); + cmd = '/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I'; + exec(cmd, function (error, stdout) { + const lines2 = stdout.toString().split('\n'); + if (lines.length > 10) { + const ssid = util.getValue(lines2, 'ssid', ':', true); + const bssid = util.getValue(lines2, 'bssid', ':', true); + const security = util.getValue(lines2, 'link auth', ':', true); + const txRate = util.getValue(lines2, 'lastTxRate', ':', true); + const channel = util.getValue(lines2, 'channel', ':', true).split(',')[0]; + const type = '802.11'; + const rssi = util.toInt(util.getValue(lines2, 'agrCtlRSSI', ':', true)); + const noise = util.toInt(util.getValue(lines2, 'agrCtlNoise', ':', true)); + const signalLevel = rssi - noise; + // const signal = wifiQualityFromDB(signalLevel); + if (ssid && bssid) { + result.push({ + id: 'Wi-Fi', + iface, + model, + ssid, + bssid, + channel: util.toInt(channel), + frequency: channel ? wifiFrequencyFromChannel(channel) : null, + type, + security, + signalLevel, + txRate + }); + + } + } + if (callback) { + callback(result); + } + resolve(result); + }); + } + }); + } else if (_windows) { + let cmd = 'netsh wlan show interfaces'; + util.powerShell(cmd).then(function (stdout, error) { + const allLines = stdout.toString().split('\r\n') + for (let i = 0; i < allLines.length; i++) { + allLines[i] = allLines[i].trim() + }; + const parts = allLines.join('\r\n').split(':\r\n\r\n'); + parts.shift(); + parts.forEach(part => { + const lines = part.split('\r\n'); + if (lines.length >= 5) { + const iface = lines[0].indexOf(':') >= 0 ? lines[0].split(':')[1].trim() : ''; + const model = lines[1].indexOf(':') >= 0 ? lines[1].split(':')[1].trim() : ''; + const id = lines[2].indexOf(':') >= 0 ? lines[2].split(':')[1].trim() : ''; + const ssid = util.getValue(lines, 'SSID', ':', true); + const bssid = util.getValue(lines, 'BSSID', ':', true); + const signalLevel = util.getValue(lines, 'Signal', ':', true); + const type = util.getValue(lines, 'Radio type', ':', true) || util.getValue(lines, 'Type de radio', ':', true) || util.getValue(lines, 'Funktyp', ':', true) || null; + const security = util.getValue(lines, 'authentication', ':', true) || util.getValue(lines, 'Authentification', ':', true) || util.getValue(lines, 'Authentifizierung', ':', true) || null; + const channel = util.getValue(lines, 'Channel', ':', true) || util.getValue(lines, 'Canal', ':', true) || util.getValue(lines, 'Kanal', ':', true) || null; + const txRate = util.getValue(lines, 'Transmit rate (mbps)', ':', true) || util.getValue(lines, 'Transmission (mbit/s)', ':', true) || util.getValue(lines, 'Empfangsrate (MBit/s)', ':', true) || null; + if (model && id && ssid && bssid) { + result.push({ + id, + iface, + model, + ssid, + bssid, + channel: util.toInt(channel), + frequency: channel ? wifiFrequencyFromChannel(channel) : null, + type, + security, + signalLevel, + txRate: util.toInt(txRate) || null + }); + } + } + }); + if (callback) { + callback(result); + } + resolve(result); + }); + } else { + if (callback) { + callback(result); + } + resolve(result); + } + }); + }); +} + +exports.wifiConnections = wifiConnections; + +function wifiInterfaces(callback) { + + return new Promise((resolve) => { + process.nextTick(() => { + const result = []; + + if (_linux) { + const ifaces = ifaceListLinux(); + ifaces.forEach(ifaceDetail => { + const nmiDetails = nmiDeviceLinux(ifaceDetail.iface); + result.push({ + id: ifaceDetail.id, + iface: ifaceDetail.iface, + model: nmiDetails.product ? nmiDetails.product : null, + vendor: nmiDetails.vendor ? nmiDetails.vendor : null, + mac: ifaceDetail.mac, + }); + }); + if (callback) { + callback(result); + } + resolve(result); + } else if (_darwin) { + let cmd = 'system_profiler SPNetworkDataType'; + exec(cmd, function (error, stdout) { + const parts1 = stdout.toString().split('\n\n Wi-Fi:\n\n'); + if (parts1.length > 1) { + const lines = parts1[1].split('\n\n')[0].split('\n'); + const iface = util.getValue(lines, 'BSD Device Name', ':', true); + const mac = util.getValue(lines, 'MAC Address', ':', true); + const model = util.getValue(lines, 'hardware', ':', true); + result.push({ + id: 'Wi-Fi', + iface, + model, + vendor: '', + mac + }); + } + if (callback) { + callback(result); + } + resolve(result); + }); + } else if (_windows) { + let cmd = 'netsh wlan show interfaces'; + util.powerShell(cmd).then(function (stdout, error) { + const allLines = stdout.toString().split('\r\n') + for (let i = 0; i < allLines.length; i++) { + allLines[i] = allLines[i].trim() + }; + const parts = allLines.join('\r\n').split(':\r\n\r\n'); + parts.shift(); + parts.forEach(part => { + const lines = part.split('\r\n'); + if (lines.length >= 5) { + const iface = lines[0].indexOf(':') >= 0 ? lines[0].split(':')[1].trim() : ''; + const model = lines[1].indexOf(':') >= 0 ? lines[1].split(':')[1].trim() : ''; + const id = lines[2].indexOf(':') >= 0 ? lines[2].split(':')[1].trim() : ''; + const macParts = lines[3].indexOf(':') >= 0 ? lines[3].split(':') : []; + macParts.shift(); + const mac = macParts.join(':').trim(); + const vendor = getVendor(model); + if (iface && model && id && mac) { + result.push({ + id, + iface, + model, + vendor, + mac, + }); + } + } + }); + if (callback) { + callback(result); + } + resolve(result); + }); + } else { + if (callback) { + callback(result); + } + resolve(result); + } + }); + }); +} + +exports.wifiInterfaces = wifiInterfaces;