Skip to content
This repository has been archived by the owner on Aug 11, 2020. It is now read-only.

Fix problem shutting down interfaces with names like wlan0-foobar sin… #109

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f292e8a
Support for setting up access points using hostapd and dnsmasq
foosel Aug 29, 2014
13eb68b
Fixed unit test
foosel Aug 29, 2014
5ec6d7c
Merge branch 'manage_missing_writeaccess'
foosel Aug 29, 2014
c839b9c
Removed hostapd output check again, doesn't always work
foosel Sep 2, 2014
1712f18
Merge branch 'access_point_support'
foosel Sep 2, 2014
064f323
Added install_extras/uninstall_extras commands to install bash comple…
foosel Sep 3, 2014
6a7ad6a
Allow configuration of Hostapd, Dnsmasq and Scheme implementation use…
foosel Sep 3, 2014
dc9091f
Fire up hostapd first, then scheme, then dnsmasq
foosel Sep 29, 2014
87667f0
Added some more logging output to wifi commands
foosel Sep 30, 2014
9e73244
Also catch any subprocess exceptions thrown by ifup and interpret the…
foosel Jan 21, 2015
60f664e
Added output to caught CalledProcessError logging upon trying to acti…
foosel Jan 28, 2015
59ec94e
Added .iml files (IntelliJ project files) to gitignore
foosel Jan 31, 2015
6503285
Raise InterfaceError instead of ConnectionError when call to ifup fai…
foosel Feb 4, 2015
f565751
Forgot an import
foosel Feb 4, 2015
35775c0
WEP passkeys in plaintext need to be prefixed with "s:" in order to f…
foosel Feb 5, 2015
14f1003
Fixed unit test
foosel Feb 5, 2015
da6aee1
Merge branch 'fix/wepPasskey'
foosel Feb 5, 2015
6c776b6
Reverted 35775c0924ecc122401595c169661e6b24d40b0d and 14f100386f34d78…
foosel Feb 10, 2015
8bb9fa6
Now only prefix obvious ASCII web keys with the s: parameter
foosel Feb 10, 2015
b7c6ed4
Fix problem shutting down interfaces with names like wlan0-foobar sin…
knro Mar 19, 2017
ef8a44a
Merge with foosel fork
knro Mar 19, 2017
43e0582
Add missing bin file
knro Mar 19, 2017
a9eb52f
Do not fail if interface is already configured
knro Apr 23, 2017
04c885e
Use interface
knro Apr 23, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ docs/_build
tmp/
.coverage
htmlcov
*.iml
205 changes: 205 additions & 0 deletions bin/wifi
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
#!/usr/bin/python
from __future__ import print_function
import argparse
import sys
import os

from wifi import Cell, Scheme
from wifi.utils import print_table, match as fuzzy_match
from wifi.exceptions import ConnectionError, InterfaceError

try: # Python 2.x
input = raw_input
except NameError:
pass


def fuzzy_find_cell(interface, query):
match_partial = lambda cell: fuzzy_match(query, cell.ssid)

matches = Cell.where(interface, match_partial)

num_unique_matches = len(set(cell.ssid for cell in matches))
assert num_unique_matches > 0, "Couldn't find a network that matches '{}'".format(query)
assert num_unique_matches < 2, "Found more than one network that matches '{}'".format(query)

# Several cells of the same SSID
if len(matches) > 1:
matches.sort(key=lambda cell: cell.signal)

return matches[0]


def find_cell(interface, query):
cell = Cell.where(interface, lambda cell: cell.ssid.lower() == query.lower())

try:
cell = cell[0]
except IndexError:
cell = fuzzy_find_cell(interface, query)
return cell


def get_scheme_params(interface, scheme, ssid=None):
cell = find_cell(interface, ssid or scheme)
passkey = None if not cell.encrypted else input('passkey> ')

return interface, scheme, cell, passkey


def scan_command(args):
print_table([[cell.signal, cell.ssid, 'protected' if cell.encrypted else 'unprotected'] for cell in Cell.all(args.interface)])


def list_command(args):
for scheme in Scheme.for_file(args.file).all():
print(scheme.name)


def show_command(args):
scheme = Scheme.for_file(args.file).for_cell(*get_scheme_params(args.interface, args.scheme, args.ssid))
print(scheme)


def add_command(args):
scheme_class = Scheme.for_file(args.file)
assert not scheme_class.find(args.interface, args.scheme), "That scheme has already been used"

scheme = scheme_class.for_cell(*get_scheme_params(args.interface, args.scheme, args.ssid))
scheme.save()


def connect_command(args):
scheme_class = Scheme.for_file(args.file)
if args.adhoc:
# ensure that we have the adhoc utility scheme
try:
adhoc_scheme = scheme_class(args.interface, 'adhoc')
adhoc_scheme.save()
except AssertionError:
pass
except IOError:
assert False, "Can't write on {0!r}, do you have required privileges?".format(args.file)

scheme = scheme_class.for_cell(*get_scheme_params(args.interface, 'adhoc', args.scheme))
else:
scheme = scheme_class.find(args.interface, args.scheme)
assert scheme, "Couldn't find a scheme named {0!r}, did you mean to use -a?".format(args.scheme)

try:
scheme.activate()
except ConnectionError:
assert False, "Failed to connect to %s." % scheme.name


def autoconnect_command(args):
ssids = [cell.ssid for cell in Cell.all(args.interface)]

for scheme in Scheme.all():
# TODO: make it easier to get the SSID off of a scheme.
ssid = scheme.options.get('wpa-ssid', scheme.options.get('wireless-essid'))
if ssid in ssids:
sys.stderr.write('Connecting to "%s".\n' % ssid)
try:
scheme.activate()
except ConnectionError:
assert False, "Failed to connect to %s." % scheme.name
break
else:
assert False, "Couldn't find any schemes that are currently available."


def arg_parser():
parser = argparse.ArgumentParser()
parser.add_argument('-i',
'--interface',
default='wlan0',
help="Specifies which interface to use (wlan0, eth0, etc.)")
parser.add_argument('-f',
'--file',
default='/etc/network/interfaces',
help="Specifies which file for scheme storage.")

subparsers = parser.add_subparsers(title='commands')

parser_scan = subparsers.add_parser('scan', help="Shows a list of available networks.")
parser_scan.set_defaults(func=scan_command)

parser_list = subparsers.add_parser('list', help="Shows a list of networks already configured.")
parser_list.set_defaults(func=list_command)

scheme_help = ("A memorable nickname for a wireless network."
" If SSID is not provided, the network will be guessed using SCHEME.")
ssid_help = ("The SSID for the network to which you wish to connect."
" This is fuzzy matched, so you don't have to be precise.")

parser_show = subparsers.add_parser('config',
help="Prints the configuration to connect to a new network.")
parser_show.add_argument('scheme', help=scheme_help, metavar='SCHEME')
parser_show.add_argument('ssid', nargs='?', help=ssid_help, metavar='SSID')
parser_show.set_defaults(func=show_command)

parser_add = subparsers.add_parser('add',
help="Adds the configuration to connect to a new network.")
parser_add.add_argument('scheme', help=scheme_help, metavar='SCHEME')
parser_add.add_argument('ssid', nargs='?', help=ssid_help, metavar='SSID')
parser_add.set_defaults(func=add_command)

parser_connect = subparsers.add_parser('connect',
help="Connects to the network corresponding to SCHEME")
parser_connect.add_argument('scheme',
help="The nickname of the network to which you wish to connect.",
metavar='SCHEME')
parser_connect.add_argument('-a',
'--ad-hoc',
dest='adhoc',
action="store_true",
help="Connect to a network without storing it in the config file")
parser_connect.set_defaults(func=connect_command)


# TODO: how to specify the correct interfaces file to work off of.
parser_connect.get_options = lambda: [scheme.name for scheme in Scheme.all()]

parser_autoconnect = subparsers.add_parser(
'autoconnect',
help="Searches for saved schemes that are currently"
" available and connects to the first one it finds."
)
parser_autoconnect.set_defaults(func=autoconnect_command)

return parser, subparsers


def autocomplete(position, wordlist, subparsers):
if position == 1:
ret = subparsers.choices.keys()
else:
try:
prev = wordlist[position - 1]
ret = subparsers.choices[prev].get_options()
except (IndexError, KeyError, AttributeError):
ret = []

print(' '.join(ret))


if __name__ == "__main__":
parser, subparsers = arg_parser()

if len(sys.argv) == 1:
argv = ['scan']
else:
argv = sys.argv[1:]

args = parser.parse_args(argv)

try:
if 'WIFI_AUTOCOMPLETE' in os.environ:
autocomplete(int(os.environ['COMP_CWORD']),
os.environ['COMP_WORDS'].split(), subparsers)
else:
args.func(args)
except (AssertionError, InterfaceError) as e:
sys.stderr.write("Error: ")
sys.exit(e)
3 changes: 2 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ Using this library, you can discover networks, connect to them, save your config

The original impetus for creating this library was my frustration with with connecting to the Internet using NetworkManager and wicd.
It is very much for computer programmers, not so much for normal computer users.
Wifi is built on top the old technologies of the `/etc/network/interfaces` file and `ifup` and `ifdown`.
Wifi is built on top the old technologies of the `/etc/network/interfaces` file and `ifup` and `ifdown` as well as
`hostapd` and `dnsmasq` for creating access points.
It is inspired by `ifscheme`.

The library also comes with an executable that you can use to manage your WiFi connections.
Expand Down
141 changes: 113 additions & 28 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python
from setuptools import setup
from setuptools import setup, Command
import os
import sys

Expand All @@ -16,38 +16,120 @@ def read(fname):
install_requires = [
'setuptools',
'pbkdf2',
'netaddr'
]
try:
import argparse
except:
install_requires.append('argparse')

version = '1.0.0'

should_install_cli = os.environ.get('WIFI_INSTALL_CLI') not in ['False', '0']
command_name = os.environ.get('WIFI_CLI_NAME', 'wifi')

if command_name == 'wifi.py':
print(
"Having a command name of wifi.py will result in a weird ImportError"
" that doesn't seem possible to work around. Pretty much any other"
" name seems to work though."
)
sys.exit(1)

entry_points = {}
data_files = []

if should_install_cli:
entry_points['console_scripts'] = [
'{command} = wifi.cli:main'.format(command=command_name),
]
# make sure we actually have write access to the target folder and if not don't
# include it in data_files
if os.access('/etc/bash_completion.d/', os.W_OK):
data_files.append(('/etc/bash_completion.d/', ['extras/wifi-completion.bash']))
version = '1.0.1'

EXTRAS = [
('/etc/bash_completion.d/', [('extras/wifi-completion.bash', 'wifi-completion', 0644)])
]


def get_extra_tuple(entry):
if isinstance(entry, (tuple, list)):
if len(entry) == 2:
path, mode = entry
filename = os.path.basename(path)
elif len(entry) == 3:
path, filename, mode = entry
elif len(entry) == 1:
path = entry[0]
filename = os.path.basename(path)
mode = None
else:
return None

else:
print("Not installing bash completion because of lack of permissions.")
path = entry
filename = os.path.basename(path)
mode = None

return path, filename, mode


class InstallExtrasCommand(Command):
description = "install extras like init scripts and config files"
user_options = [("force", "F", "force overwriting files if they already exist")]

def initialize_options(self):
self.force = None

def finalize_options(self):
if self.force is None:
self.force = False

def run(self):
global EXTRAS
import shutil
import os

for target, files in EXTRAS:
for entry in files:
extra_tuple = get_extra_tuple(entry)
if extra_tuple is None:
print("Can't parse entry for target %s, skipping it: %r" % (target, entry))
continue

path, filename, mode = extra_tuple
target_path = os.path.join(target, filename)

path_exists = os.path.exists(target_path)
if path_exists and not self.force:
print("Skipping copying %s to %s as it already exists, use --force to overwrite" % (path, target_path))
continue

try:
shutil.copy(path, target_path)
if mode:
os.chmod(target_path, mode)
print("Copied %s to %s and changed mode to %o" % (path, target_path, mode))
else:
print("Copied %s to %s" % (path, target_path))
except Exception as e:
if not path_exists and os.path.exists(target_path):
# we'll try to clean up again
try:
os.remove(target_path)
except:
pass

import sys
print("Error while copying %s to %s (%s), aborting" % (path, target_path, e.message))
sys.exit(-1)


class UninstallExtrasCommand(Command):
description = "uninstall extras like init scripts and config files"
user_options = []

def initialize_options(self):
pass

def finalize_options(self):
pass

def run(self):
global EXTRAS
import os

for target, files in EXTRAS:
for entry in files:
extra_tuple = get_extra_tuple(entry)
if extra_tuple is None:
print("Can't parse entry for target %s, skipping it: %r" % (target, entry))

path, filename, mode = extra_tuple
target_path = os.path.join(target, filename)
try:
os.remove(target_path)
print("Removed %s" % target_path)
except Exception as e:
print("Error while deleting %s from %s (%s), please remove manually" % (filename, target, e.message))

setup(
name='wifi',
Expand All @@ -57,7 +139,7 @@ def read(fname):
description=__doc__,
long_description='\n\n'.join([read('README.rst'), read('CHANGES.rst')]),
packages=['wifi'],
entry_points=entry_points,
scripts=['bin/wifi'],
test_suite='tests',
platforms=["Debian"],
license='BSD',
Expand All @@ -72,5 +154,8 @@ def read(fname):
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.3",
],
data_files=data_files
cmdclass={
'install_extras': InstallExtrasCommand,
'uninstall_extras': UninstallExtrasCommand
}
)
Loading