Skip to content

Commit

Permalink
Windows optimisations
Browse files Browse the repository at this point in the history
  • Loading branch information
needs-coffee committed Mar 25, 2021
1 parent 8a455b4 commit 38f7b12
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 53 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ Usage
from wifipasswords import WifiPasswords

passwords = WifiPasswords().get_passwords()
connected_ssids = WifiPasswords().get_currently_connected_ssids()
connected_passwords = WifiPasswords().get_currently_connected_passwords()

print(passwords)
print(connected_passwords)

WifiPasswords().save_wpa_supplicant('.', passwords, True, 'GB')
```
Expand Down Expand Up @@ -67,14 +68,14 @@ To-Do
- [X] Add getters for accessing variables directly
- [ ] Fix visible network, DNS config and number of interfaces for Linux
- [ ] Add automated tests
- [ ] Add method to check and return only the connected SSID name and Password
- [X] Add method to check and return only the connected SSID name and Password
- [ ] Use nmcli to retrieve passwords on linux rather than reading files (may not require sudo)

- [X] Multithreading support for windows to imporove execution speed

About
-----
Creation date: 10-02-2019
Modified date: 23-03-2021
Modified date: 25-03-2021
Dependencies: colorama


Expand Down
11 changes: 11 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,24 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).



## 0.3.3b - 25-03-2021
### Added
- added get_currently_connected_passwords() to return current connected SSID and psk only
### Changed
- subprocess command calls split out into separate function for code clarity/efficiency
- Multithreading support in windows to get_passwords() -> execution time reduced from 2.1s to 0.6s for 24 networks


## 0.3.2b - 24-03-2021
### Added
- Getters for accessing variables directly from WifiPasswords class
### Changed
- fixed import issue in wifipasswords_exe
- fixed key error in get_passwords_dummy()


## 0.3.1b - 23-03-2021
### Added
- Added support to detect mac-randomisation
Expand Down
11 changes: 9 additions & 2 deletions wifipasswords/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
Uses the netsh windows module. Pass --JSON argument to export as JSON.
Pass --wpasupplicant to create a wpa_supplicant.conf file for linux
Creation date: 10-02-2019
Modified date: 24-03-2021
Modified date: 25-03-2021
Dependencies: colorama
"""
__copyright__ = "Copyright (C) 2019-2021 Joe Campbell"
Expand All @@ -25,7 +25,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see < https: // www.gnu.org/licenses/>.

__version__ = "0.3.2-beta"
__version__ = "0.3.3-beta"
__licence__ = "GPLv3" # GNU General Public Licence v3

import platform
Expand Down Expand Up @@ -190,3 +190,10 @@ def get_currently_connected_ssids(self) -> list:
checks all active interfaces on the system. \n
"""
return self._WifiPasswordsSubclass.get_currently_connected_ssids()


def get_currently_connected_passwords(self) -> list:
"""
Returns a tuple of (ssid, psk) for each currently connected network as a list.
"""
return self._WifiPasswordsSubclass.get_currently_connected_passwords()
11 changes: 11 additions & 0 deletions wifipasswords/wifipasswords_linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ def get_passwords(self) -> dict:
network_blocks = re.findall('(?<=network=)[^}]*(?=})',file_string)
for network_block in network_blocks:
block_stripped = network_block.strip().replace('\t','').replace('\n',' ').split(' ')
ssid = ' '
auth = ' '
psk = ' '
for item in block_stripped:
if 'ssid' in item:
ssid = item.split('ssid=')[1][1:-1]
Expand Down Expand Up @@ -267,3 +270,11 @@ def get_currently_connected_ssids(self) -> list:
connected_ssids.append(item)

return connected_ssids

##
## to be implemented
def get_currently_connected_passwords(self) -> list:
"""
Returns a tuple of (ssid, psk) for each currently connected network.
"""
return [('','')]
110 changes: 63 additions & 47 deletions wifipasswords/wifipasswords_windows.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# windows specific version of class
# imported as subclass to main WifiPasswords class in __init__
# functions are 1:1 maapping of stub funcitons in WifiPasswords with platform specific code
# exposed functions are 1:1 maapping of stub funcitons in WifiPasswords with platform specific code
# documentation for funcitons provided only in main __init__ WifiPasswords class as is the only class designed to be exposed

__copyright__ = "Copyright (C) 2019-2021 Joe Campbell"
Expand All @@ -22,10 +22,10 @@
import os
import json
import re
from multiprocessing.dummy import Pool as ThreadPool

from . import __version__, __copyright__, __licence__


class WifiPasswordsWindows:
def __init__(self) -> None:
self.data = {}
Expand All @@ -35,41 +35,60 @@ def __init__(self) -> None:
self.net_template = {'auth': '', 'psk': '',
'metered': False, 'macrandom': 'Disabled'}

def get_passwords(self) -> dict:

@staticmethod
def _command_runner(shell_commands:list) -> str:
"""
Split subprocess calls into separate runner module for clarity of code.\n
Takes the command to execute as a subprocess in the form of a list.\n
Returns the string output as a utf-8 decoded output.\n
"""
# need to use pipes for all STDIO on windows if running without interactive console.
# STARTUPINFO is only present on windows, not linux
si = subprocess.STARTUPINFO()
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
profiles_list = subprocess.run(['netsh', 'wlan', 'show', 'profiles'],
return_data = subprocess.run(shell_commands,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
startupinfo=si).stdout.decode('utf-8').split('\r\n')
startupinfo=si).stdout.decode('utf-8')
return return_data

networks = {(row.split(': ')[1]): self.net_template.copy()
for row in profiles_list if "User Profile" in row}

for net, value in networks.items():
profile_info = subprocess.run(['netsh', 'wlan', 'show', 'profile', net, 'key=clear'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
startupinfo=si).stdout.decode('utf-8').split('\r\n')
def _get_password_subthread(self,network):
# network is a tuple from the networks dictionary
# values are (ssid, value dictionary)
profile_info = self._command_runner(
['netsh', 'wlan', 'show', 'profile', network[0], 'key=clear']).split('\r\n')

for row in profile_info:
if "Key Content" in row:
value['psk'] = row.split(': ')[1].strip()
if "Authentication" in row:
value['auth'] = row.split(': ')[1].strip()
if "Cost" in row:
if "Fixed" in row or "Variable" in row:
value['metered'] = True
if "MAC Randomization" in row:
value['macrandom'] = row.split(': ')[1].strip()
for row in profile_info:
if "Key Content" in row:
network[1]['psk'] = row.split(': ')[1].strip()
if "Authentication" in row:
network[1]['auth'] = row.split(': ')[1].strip()
if "Cost" in row:
if "Fixed" in row or "Variable" in row:
network[1]['metered'] = True
if "MAC Randomization" in row:
network[1]['macrandom'] = row.split(': ')[1].strip()
return network

self.number_of_profiles = len(networks)
self.data = networks
return networks

def get_passwords(self) -> dict:
profiles_list = self._command_runner(
['netsh', 'wlan', 'show', 'profiles']).split('\r\n')

networks = {(row.split(': ')[1]): self.net_template.copy()
for row in profiles_list if "Profile :" in row}

# from testing 6 seems the optimum thread number
pool = ThreadPool(6)
results = dict(pool.imap(self._get_password_subthread,networks.items()))
pool.close()
pool.join()
self.number_of_profiles = len(results)
self.data = results
return results


def get_passwords_dummy(self, delay: float = 0.5, quantity: int = 10) -> dict:
Expand Down Expand Up @@ -98,13 +117,7 @@ def get_passwords_data(self) -> dict:


def get_visible_networks(self, as_dictionary=False) -> str:
si = subprocess.STARTUPINFO()
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
current_networks = subprocess.run(['netsh', 'wlan', 'show', 'networks', 'mode=Bssid'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
startupinfo=si).stdout.decode('utf-8')
current_networks = self._command_runner(['netsh', 'wlan', 'show', 'networks', 'mode=Bssid'])

if "powered down" in current_networks:
self.number_visible_networks = 0
Expand Down Expand Up @@ -161,13 +174,7 @@ def get_visible_networks(self, as_dictionary=False) -> str:


def get_dns_config(self, as_dictionary=False) -> str:
si = subprocess.STARTUPINFO()
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
dns_settings = subprocess.run(['netsh', 'interface', 'ip', 'show', 'dns'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
startupinfo=si).stdout.decode('utf-8')
dns_settings = self._command_runner(['netsh', 'interface', 'ip', 'show', 'dns'])

split_dns_config = dns_settings.strip().split('\r\n\r\n')
self.number_of_interfaces = len(split_dns_config)
Expand Down Expand Up @@ -258,18 +265,27 @@ def get_number_profiles(self) -> int:

def get_currently_connected_ssids(self) -> list:
connected_ssids = []
current_interfaces = self._command_runner(['netsh', 'wlan', 'show', 'interfaces']).split('\r\n')

si = subprocess.STARTUPINFO()
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
current_interfaces = subprocess.run(['netsh', 'wlan', 'show', 'interfaces'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
startupinfo=si).stdout.decode('utf-8').split('\r\n')
for line in current_interfaces:
# space before ssid prevents BSSID being captured
if " SSID" in line:
#what if ssid contains : ? could it? would need workaround logic if so
connected_ssids.append(line.split(':')[1].strip())

return connected_ssids


def get_currently_connected_passwords(self) -> list:
connected_passwords = []
connected_ssids = self.get_currently_connected_ssids()

for ssid in connected_ssids:
key_data = self._command_runner(['netsh', 'wlan', 'show', 'profile', ssid, 'key=clear']).split('\r\n')
psk = ''
for row in key_data:
if "Key Content" in row:
psk = row.split(': ')[1].strip()
connected_passwords.append((ssid,psk))

return connected_passwords

0 comments on commit 38f7b12

Please sign in to comment.