willow_run 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +83 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/all_ssid_strs.rb +21 -0
- data/examples/all_ssid_strs_and_bssids.rb +25 -0
- data/examples/current_connected_ap_data.rb +23 -0
- data/examples/example.rb +5 -0
- data/examples/first_ap_data.rb +23 -0
- data/examples/simple_sniffer.rb +14 -0
- data/lib/willow_run.rb +52 -0
- data/lib/willow_run/access_point.rb +37 -0
- data/lib/willow_run/airport.rb +4 -0
- data/lib/willow_run/errors.rb +18 -0
- data/lib/willow_run/generate_psk.rb +36 -0
- data/lib/willow_run/parser.rb +61 -0
- data/lib/willow_run/scanner.rb +30 -0
- data/lib/willow_run/sniffer.rb +130 -0
- data/lib/willow_run/status.rb +31 -0
- data/lib/willow_run/version.rb +3 -0
- data/willow_run.gemspec +26 -0
- metadata +146 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1b6a01b45bd102244d8f6b96c6d539f9caf6aa89
|
4
|
+
data.tar.gz: a63cec69b7d3966915b1ff3196fa950da412b22c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 51e635916ad95a6fecfa2e6fc18a819b4020922040510cf44a34640c908eb457251799b943277dcb66c480d4b069acef7a0460c6d9ddd5f4867927b39f0487cf
|
7
|
+
data.tar.gz: cb88db8dd6ce8171db5b2a0e5aaec214e4bc93b0720a89419e7f83f3bfa20d08d13356c8bb2d3c9a955ff1a83b15cfd045870c8a305f1e568b1e6d274a45a026
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at kgruber1@emich.edu. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Kent Gruber
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# Willow Run ✈️
|
2
|
+
|
3
|
+
## ⚠️ Still in development! Should have working tests though!
|
4
|
+
|
5
|
+
#### ⚠️ Note: I've learned that `airport` sometimes dosen't handle BSSID values properly.
|
6
|
+
|
7
|
+
Willow Run is a Ruby API to the macOs/OSX `airport` command. The [airport](http://osxdaily.com/2007/01/18/airport-the-little-known-command-line-wireless-utility/) command manages 802.11 interfaces, can perform wireless broadcast scans ( with xml output support ), set arbitrary channels on the interface, and even generate PSKs from specified pass phrase and SSIDs -- all from the command-line, which I really love.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
It will be a [gem](https://rubygems.org/)! So, you'll be able to install it yourself pretty easily:
|
12
|
+
|
13
|
+
$ gem install willow_run
|
14
|
+
|
15
|
+
## Purpose
|
16
|
+
This gem takes those capabilities provided by the `airpot` command and provides a Ruby API to be able use these functionalities in an [OO](https://en.wikipedia.org/wiki/Object-oriented_programming) manner, which I also really love. This provides better flexibility to build tools ontop of this tool; and can provide greater functionality for scripting or creating applications around various wireless taks this utility provides on macOS or OSX systems in Ruby, which I happen to use sometimes.
|
17
|
+
|
18
|
+
I can also just build better command-line applications with this interface, since I really enjoy building command-line application in Ruby. Maybe you'll find it useful too.
|
19
|
+
|
20
|
+
### Why The Name?
|
21
|
+
|
22
|
+
[Willow Run](https://en.wikipedia.org/wiki/Willow_Run_Airport) is an airport located in Ypsilanti, Michigan. I go to school at [Eastern Michigan University](https://www.emich.edu/) which is also in Ypsilanti. So, it all works out like that.
|
23
|
+
|
24
|
+

|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
This gem has lots of different functionality!
|
29
|
+
|
30
|
+
### Sniffing
|
31
|
+
|
32
|
+
You can access the `airport <interface> sniff <channel>` functionality for sniffing 802.11 frames:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
require 'willow_run'
|
36
|
+
|
37
|
+
# make new sniffer object that sniffs for 10 seconds
|
38
|
+
sniffer = WillowRun::Sniffer.new(:timeout => 10)
|
39
|
+
|
40
|
+
# get the capture file path that was generated
|
41
|
+
sniffer.file
|
42
|
+
# => /tmp/airportSniff9wFMP4.cap
|
43
|
+
```
|
44
|
+
|
45
|
+
### Generate PSKs
|
46
|
+
|
47
|
+
You can use the `airport -P` command to generate a PSK:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
require 'willow_run'
|
51
|
+
|
52
|
+
WillowRun::GeneratePsk.new.generate(:ssid => "cats", :password => "dogs")
|
53
|
+
# => "4e471f8d03afeca8a9d27f6a6358294ce0e89543880fad99498ac457e0b09341"
|
54
|
+
```
|
55
|
+
|
56
|
+
### Currect Access Point Information
|
57
|
+
|
58
|
+
You can use the `airprot -I` command to get current wireless status, e.g. signal info, BSSID, port type etc:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
require 'willow_run'
|
62
|
+
|
63
|
+
# get current wireless status
|
64
|
+
info = WillowRun::Status.new.getinfo
|
65
|
+
|
66
|
+
# note: osx/macOS totally fails and gives invalid/faulty BSSID values for this command
|
67
|
+
info.bssid
|
68
|
+
# => "6c:b0:ce:0b:ec:56"
|
69
|
+
|
70
|
+
# but generally, everything is fine and dandy!
|
71
|
+
info.ssid
|
72
|
+
# => "NETGEAR52"
|
73
|
+
|
74
|
+
# and things should generally be Integers when appropriate
|
75
|
+
info.channel
|
76
|
+
# => 11
|
77
|
+
|
78
|
+
```
|
79
|
+
|
80
|
+
## License
|
81
|
+
|
82
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
83
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "willow_run"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
$: << File.expand_path("../../lib", __FILE__)
|
2
|
+
require 'willow_run'
|
3
|
+
|
4
|
+
# make new scanner object
|
5
|
+
scanner = WillowRun::Scanner.new
|
6
|
+
|
7
|
+
# scan with that scanner object
|
8
|
+
# but only get the first access point
|
9
|
+
# if it exists
|
10
|
+
ap_data = scanner.scan
|
11
|
+
|
12
|
+
unless ap_data.empty?
|
13
|
+
ap_data.each do |access_point|
|
14
|
+
puts access_point.data.ssid_str
|
15
|
+
end
|
16
|
+
else
|
17
|
+
puts "Unable to find any access points!"
|
18
|
+
exit 1
|
19
|
+
end
|
20
|
+
|
21
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
$: << File.expand_path("../../lib", __FILE__)
|
2
|
+
require 'willow_run'
|
3
|
+
|
4
|
+
# make new scanner object
|
5
|
+
scanner = WillowRun::Scanner.new
|
6
|
+
|
7
|
+
# scan with that scanner object
|
8
|
+
# but only get the first access point
|
9
|
+
# if it exists
|
10
|
+
ap_data = scanner.scan
|
11
|
+
|
12
|
+
unless ap_data.empty?
|
13
|
+
puts "-" * 40
|
14
|
+
puts "%-20s |\t%s" % [ "BSSID", "SSID" ]
|
15
|
+
puts "-" * 40
|
16
|
+
ap_data.each do |access_point|
|
17
|
+
data = access_point.data
|
18
|
+
puts "%-20s |\t%s" % [ data.bssid, data.ssid_str ]
|
19
|
+
end
|
20
|
+
else
|
21
|
+
puts "Unable to find any access points!"
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
|
25
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
$: << File.expand_path("../../lib", __FILE__)
|
2
|
+
require 'willow_run'
|
3
|
+
|
4
|
+
# get current wireless status, e.g. signal info, BSSID, port type etc.
|
5
|
+
info = WillowRun::Status.new.getinfo
|
6
|
+
|
7
|
+
# print out that information all purtty like
|
8
|
+
# closely mimics the `airport -I` command
|
9
|
+
puts "agrCtlRSSI : " + info.agrctlrssi.to_s
|
10
|
+
puts "agrExtRSSI : " + info.agrextrssi.to_s
|
11
|
+
puts "agrCtlNoise : " + info.agrctlnoise.to_s
|
12
|
+
puts "agrExtNoise : " + info.agrextnoise.to_s
|
13
|
+
puts "state : " + info.state.to_s
|
14
|
+
puts "op mode : " + info.op_mode.to_s
|
15
|
+
puts "lastTxRate : " + info.lasttxrate.to_s
|
16
|
+
puts "maxRate : " + info.maxrate.to_s
|
17
|
+
puts "lastAssocStatus : " + info.lastassocstatus.to_s
|
18
|
+
puts "802.11 auth : " + info.wifi_auth.to_s
|
19
|
+
puts "link auth : " + info.link_auth.to_s
|
20
|
+
puts "BSSID : " + info.bssid.to_s
|
21
|
+
puts "SSID : " + info.ssid.to_s
|
22
|
+
puts "MCS : " + info.mcs.to_s
|
23
|
+
puts "channel : " + info.channel.to_s
|
data/examples/example.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
$: << File.expand_path("../../lib", __FILE__)
|
2
|
+
require 'willow_run'
|
3
|
+
|
4
|
+
# make new scanner object
|
5
|
+
scanner = WillowRun::Scanner.new
|
6
|
+
|
7
|
+
# scan with that scanner object
|
8
|
+
# but only get the first access point
|
9
|
+
# if it exists
|
10
|
+
ap = scanner.scan[0] || false
|
11
|
+
|
12
|
+
# if there is any data to work with
|
13
|
+
if ap
|
14
|
+
ap.data.each_pair do |key,value|
|
15
|
+
puts "#{key} : #{value}"
|
16
|
+
end
|
17
|
+
else
|
18
|
+
# otherwise just error out
|
19
|
+
puts "Unable to find any access points!"
|
20
|
+
exit 1
|
21
|
+
end
|
22
|
+
|
23
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
$: << File.expand_path("../../lib", __FILE__)
|
2
|
+
require 'willow_run'
|
3
|
+
|
4
|
+
# make new sniffer object
|
5
|
+
sniffer = WillowRun::Sniffer.new
|
6
|
+
|
7
|
+
# start sniffing
|
8
|
+
# The default timeout after 5 seconds
|
9
|
+
# has been set to 10 seconds.
|
10
|
+
sniffer.sniff(:timeout => 10)
|
11
|
+
|
12
|
+
# get the capture file path that was generated
|
13
|
+
sniffer.file
|
14
|
+
# => /tmp/airportSniff9wFMP4.cap
|
data/lib/willow_run.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'plist'
|
3
|
+
require 'open3'
|
4
|
+
require 'socket'
|
5
|
+
require 'packetfu'
|
6
|
+
require 'plist'
|
7
|
+
require "willow_run/airport"
|
8
|
+
require "willow_run/access_point"
|
9
|
+
require "willow_run/generate_psk"
|
10
|
+
require "willow_run/scanner"
|
11
|
+
require "willow_run/sniffer"
|
12
|
+
require "willow_run/parser"
|
13
|
+
require "willow_run/status"
|
14
|
+
require "willow_run/errors"
|
15
|
+
require "willow_run/version"
|
16
|
+
|
17
|
+
module WillowRun
|
18
|
+
|
19
|
+
# find_the_airport?() will help determine if the airport
|
20
|
+
# Mach-O 64-bit executable is avaiable to us or not.
|
21
|
+
def self.find_the_airport?
|
22
|
+
File.exist?(AIRPORT) ? true : false
|
23
|
+
end
|
24
|
+
|
25
|
+
# take_off?() can help determine if the airport
|
26
|
+
# command is accessible or not if we run it.
|
27
|
+
def self.take_off?
|
28
|
+
Open3.capture2(AIRPORT).last.success?
|
29
|
+
end
|
30
|
+
|
31
|
+
# set_channel() allows a user to set a
|
32
|
+
# arbitrary channel on the card.
|
33
|
+
def self.set_channel(channel)
|
34
|
+
o, s = Open3.capture2("#{AIRPORT} -c #{channel}")
|
35
|
+
s.success? ? true : false
|
36
|
+
end
|
37
|
+
|
38
|
+
# disassociate() allows a user to simply
|
39
|
+
# disassociate from any network, requiring
|
40
|
+
# root/sudo privilleges
|
41
|
+
def self.disassociate
|
42
|
+
unless Process.uid == 0
|
43
|
+
o, s = Open3.capture2("#{AIRPORT} -z")
|
44
|
+
unless o == "root required to disassociate\n"
|
45
|
+
return true
|
46
|
+
end
|
47
|
+
raise WillowRunError.new(o)
|
48
|
+
false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module WillowRun
|
2
|
+
|
3
|
+
# AccessPoint maintains the logic to do with
|
4
|
+
# the access points found during a scan. Data
|
5
|
+
# asccoiated with an access point is avaiable via
|
6
|
+
# the data attr_accessor associated with it.
|
7
|
+
#
|
8
|
+
# == Example
|
9
|
+
#
|
10
|
+
# # Typical use
|
11
|
+
# ap = AccessPoint(xml_pist_data_from_scan)
|
12
|
+
# # access that access point's data
|
13
|
+
# ap.data
|
14
|
+
# # get the bssid of the access point
|
15
|
+
# ap.data.bssid
|
16
|
+
# # => "6c:b0:ce:b:ec:56"
|
17
|
+
# # get the channel of the access point
|
18
|
+
# ap.data.channel
|
19
|
+
# # => 11
|
20
|
+
# # get the ssid string associated with the ap
|
21
|
+
# ap.data.ssid_str
|
22
|
+
# # => "NETGEAR53"
|
23
|
+
#
|
24
|
+
class AccessPoint
|
25
|
+
# data contains the OpenStruct data for the AccessPoint
|
26
|
+
attr_accessor :data
|
27
|
+
|
28
|
+
def initialize(xml_plist)
|
29
|
+
# downcase the main keys of the hash to be
|
30
|
+
# able to turn them into methods using OpenStruct
|
31
|
+
struct_data = Hash[xml_plist.map{ |k, v| [k.downcase, v] }]
|
32
|
+
# parse the data by storing data it into an OpenStruct
|
33
|
+
@data = OpenStruct.new(struct_data)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module WillowRun
|
2
|
+
|
3
|
+
# WillowRunError handles the custom error handling for this Gem
|
4
|
+
#
|
5
|
+
# == Example
|
6
|
+
#
|
7
|
+
# # Typical use case
|
8
|
+
# raise WillowRunError.new("This is a custom error!")
|
9
|
+
#
|
10
|
+
class WillowRunError < StandardError
|
11
|
+
attr_reader :problem
|
12
|
+
def initialize(problem="Willow Run seems to have encountered a problem.")
|
13
|
+
@problem = problem
|
14
|
+
super(@problem)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module WillowRun
|
2
|
+
|
3
|
+
class GeneratePsk
|
4
|
+
# contains the generated PSK when avaiable.
|
5
|
+
attr_reader :psk
|
6
|
+
|
7
|
+
# generate() will generate a PSK from
|
8
|
+
# a specified pass phrase and SSID passed in
|
9
|
+
# as a hash.
|
10
|
+
#
|
11
|
+
# == Example
|
12
|
+
#
|
13
|
+
# # Typical use
|
14
|
+
# WillowRun::GeneratePsk.new.generate(:ssid => "dogs", :password => "cats")
|
15
|
+
# # => "ddd3da4ed028b81de13ed6ec53238838755bf44e69365cc6453cdcb65d42406f"
|
16
|
+
#
|
17
|
+
def generate(opts)
|
18
|
+
if opts
|
19
|
+
if opts[:ssid] and opts[:password]
|
20
|
+
o, s = Open3.capture2("#{AIRPORT} -P --ssid #{opts[:ssid]} --password #{opts[:password]}")
|
21
|
+
if s.success?
|
22
|
+
@psk = o.strip
|
23
|
+
else
|
24
|
+
raise WillowRunError.new("Unable to generate psk with SSID: #{opts[:ssid]} and PASSWORD: #{opts[:password]}")
|
25
|
+
end
|
26
|
+
else
|
27
|
+
raise WillowRunError.new("Unable to generate psk, requires both an ssid and password; only got: #{opts}.")
|
28
|
+
end
|
29
|
+
else
|
30
|
+
raise WillowRunError.new("Unable to generate psk, requires both an ssid and password.")
|
31
|
+
end
|
32
|
+
@psk
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module WillowRun
|
2
|
+
|
3
|
+
# Parser contains the logic to do with parsing xml/plist
|
4
|
+
# data for this gem. The parsed data is avaiable via
|
5
|
+
# the data attribute which should contain an array
|
6
|
+
# of AccessPoint objects after parsing.
|
7
|
+
#
|
8
|
+
# == Example
|
9
|
+
#
|
10
|
+
# # Typical use for the Parser class
|
11
|
+
# parser = WillowRun::Parser.new
|
12
|
+
# data = parser.parse(plist_xml_data)
|
13
|
+
#
|
14
|
+
class Parser
|
15
|
+
# You can either create a parser object with
|
16
|
+
# or without data when it is iniatilized.
|
17
|
+
def initialize(data=false)
|
18
|
+
parse(data) if data
|
19
|
+
end
|
20
|
+
|
21
|
+
# parse() handles the parsing of the general
|
22
|
+
# data associated with this gem where appropriate
|
23
|
+
def parse(data)
|
24
|
+
if data.is_a? Sniffer
|
25
|
+
parse_sniffer_data(data)
|
26
|
+
else
|
27
|
+
# The preferred way to store property lists on
|
28
|
+
# OS X and iOS is as an XML file called an
|
29
|
+
# XML property list or XML plist.
|
30
|
+
parse_plist_xml(data)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# parse_plist_xml() hanldes the plist xml format
|
37
|
+
# that actually is parse behind the seens to make the
|
38
|
+
# parse method prettier really.
|
39
|
+
def parse_plist_xml(data)
|
40
|
+
# Parse the xml with Plist and iterate over each
|
41
|
+
# access point by creating a new access point.
|
42
|
+
parsed_data = []
|
43
|
+
Plist::parse_xml(data).each do |ap_info|
|
44
|
+
ap = AccessPoint.new(ap_info)
|
45
|
+
parsed_data << ap
|
46
|
+
end
|
47
|
+
parsed_data
|
48
|
+
end
|
49
|
+
|
50
|
+
# parse_sniffer_data() handles the tcpdump format
|
51
|
+
# that is generated by the airport's sniffing
|
52
|
+
# ability using usual pcap tools.
|
53
|
+
def parse_sniffer_data(data)
|
54
|
+
# note: will not be able to parse every thing...
|
55
|
+
# better than ruby-pcap which just fails for pcap
|
56
|
+
# things it dosen't know how to handle.
|
57
|
+
PacketFu::PcapFile.file_to_array data.file
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module WillowRun
|
2
|
+
|
3
|
+
class Scanner
|
4
|
+
def initialzie(opts=false)
|
5
|
+
if opts[:scan]
|
6
|
+
if opts[:ssid]
|
7
|
+
scan(opts[:ssid])
|
8
|
+
else
|
9
|
+
scan
|
10
|
+
end
|
11
|
+
end
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def scan(ssid="")
|
16
|
+
o, s = Open3.capture2("#{AIRPORT} -x -s '#{ssid}'")
|
17
|
+
manage_output(o, s)
|
18
|
+
end
|
19
|
+
|
20
|
+
def manage_output(stdout, status)
|
21
|
+
if status.success?
|
22
|
+
Parser.new.parse(stdout)
|
23
|
+
else
|
24
|
+
# custom error?
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module WillowRun
|
2
|
+
|
3
|
+
# Sniffing contains the logic to do with sniffing
|
4
|
+
# 802.11 frames on an interface using the airport
|
5
|
+
# command's sniff verb.
|
6
|
+
#
|
7
|
+
# == Example
|
8
|
+
#
|
9
|
+
# # Typical use for the Parser class
|
10
|
+
# sniffer = WillowRun::Sniffer.new
|
11
|
+
# # sniff for 30 seconds
|
12
|
+
# sniffer.sniff(:time => 30)
|
13
|
+
#
|
14
|
+
class Sniffer
|
15
|
+
# file contains the path to the tcpdump capture file
|
16
|
+
# version 2.4, 802.11 with radiotap header
|
17
|
+
attr_accessor :file
|
18
|
+
# pcap contains the parsed tcpdump data
|
19
|
+
attr_accessor :pcap
|
20
|
+
# interface is the interface to capture on
|
21
|
+
attr_accessor :interface
|
22
|
+
|
23
|
+
# You can either create a parser object with
|
24
|
+
# or without data when it is iniatilized.
|
25
|
+
def initialize(opts=false)
|
26
|
+
if opts
|
27
|
+
if opts[:file]
|
28
|
+
unless File.exists?(opts[:file])
|
29
|
+
raise WillowRunError.new("#{opts[:file]} doesn't seem to exist!")
|
30
|
+
else
|
31
|
+
@file = opts[:file]
|
32
|
+
end
|
33
|
+
return true
|
34
|
+
end
|
35
|
+
if opts[:sniff]
|
36
|
+
if opts[:channel] and opts[:interface]
|
37
|
+
sniff(opts[:channel],opts[:interface])
|
38
|
+
elsif opts[:interface]
|
39
|
+
sniff("",opts[:interface])
|
40
|
+
elsif opts[:channel]
|
41
|
+
sniff(opts[:channel])
|
42
|
+
else
|
43
|
+
sniff
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
50
|
+
# sniff() alllows the capturing packets, as long as
|
51
|
+
# the process as the correct privelleges. Optionally,
|
52
|
+
# a set of options can be passed in as a hash to set
|
53
|
+
# custom parameters for the sniffing. A default of 5
|
54
|
+
# seconds has been given to the sniff method, whereafter
|
55
|
+
# the sniffing will stop / the process PID be killed.
|
56
|
+
def sniff(opts=false)
|
57
|
+
if Process.uid == 0
|
58
|
+
if opts
|
59
|
+
interface = opts[:interface] if opts[:interface]
|
60
|
+
channel = opts[:channel] if opts[:channel]
|
61
|
+
timeout = opts[:timeout] if opts[:timeout]
|
62
|
+
end
|
63
|
+
interface = default_interface unless interface
|
64
|
+
channel = "" unless channel
|
65
|
+
timeout = 5 unless timeout
|
66
|
+
stdin, stdout, stderr, wait_thr = Open3.popen3("#{AIRPORT} #{interface} sniff #{channel}")
|
67
|
+
sleep(timeout.to_i)
|
68
|
+
if Process.kill("KILL", wait_thr.pid)
|
69
|
+
@file = determine_cap_file
|
70
|
+
@pcap = Parser.new.parse(self)
|
71
|
+
else
|
72
|
+
raise WillowRunError.new("unable to kill pid #{wait_thr.pid}")
|
73
|
+
end
|
74
|
+
else
|
75
|
+
raise WillowRunError.new("sniffing must be done with root!")
|
76
|
+
end
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
# Determine the default ip address, taken from packetfu's utils.rb
|
81
|
+
def default_ip
|
82
|
+
begin
|
83
|
+
orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily
|
84
|
+
UDPSocket.open do |s|
|
85
|
+
s.connect rand_routable_daddr.to_s, rand_port
|
86
|
+
s.addr.last
|
87
|
+
end
|
88
|
+
ensure
|
89
|
+
Socket.do_not_reverse_lookup = orig
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Determine the default routeable interface, taken from packetfu's utils.rb
|
94
|
+
def default_interface
|
95
|
+
ip = default_ip
|
96
|
+
Socket.getifaddrs.each do |ifaddr|
|
97
|
+
next unless ifaddr.addr.ip?
|
98
|
+
return ifaddr.name if ifaddr.addr.ip_address == ip
|
99
|
+
end
|
100
|
+
# Fall back to libpcap as last resort
|
101
|
+
return Pcap.lookupdev
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
# Since 177/8 is IANA reserved (for now), this network should
|
107
|
+
# be handled by your default gateway and default interface.
|
108
|
+
# Taken from packetfu's utils.rb logic.
|
109
|
+
def rand_routable_daddr
|
110
|
+
IPAddr.new((rand(16777216) + 2969567232), Socket::AF_INET)
|
111
|
+
end
|
112
|
+
|
113
|
+
# A helper for getting a random port number, taken from packetfu's utils.rb
|
114
|
+
def rand_port
|
115
|
+
rand(0xffff-1024)+1024
|
116
|
+
end
|
117
|
+
|
118
|
+
# Since the airport command on Apple's part has no easy way to access
|
119
|
+
# the file name/path for the pcap that is generated during
|
120
|
+
# the sniffing process. By default, and without any options, the
|
121
|
+
# pcap file will be generated in /tmp as *.cap file with a random
|
122
|
+
# name associated with it.
|
123
|
+
def determine_cap_file
|
124
|
+
# We use the most recently modified file, best we can really
|
125
|
+
# do in this case.
|
126
|
+
Dir.glob("/tmp/*.cap").max_by {|f| File.mtime(f)}
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module WillowRun
|
2
|
+
class Status
|
3
|
+
# data contains information about the wireless status,
|
4
|
+
# e.g. signal info, BSSID, port type etc. provided
|
5
|
+
# by the airport command.
|
6
|
+
attr_reader :data
|
7
|
+
|
8
|
+
# getinfo() get the current information associated
|
9
|
+
# with the access point that the computer is already
|
10
|
+
# connected to.
|
11
|
+
def getinfo
|
12
|
+
o, s = Open3.capture2("#{AIRPORT} -I")
|
13
|
+
if s.success?
|
14
|
+
data = o.split("\n").map(&:strip)
|
15
|
+
hashed_data = {}
|
16
|
+
data.each do |info|
|
17
|
+
key, value = info.gsub(' ','_').split(":_")
|
18
|
+
key = key.gsub(':','').gsub('.','').downcase
|
19
|
+
key = "wifi_auth" if key == "80211_auth"
|
20
|
+
value = Integer(value) rescue value
|
21
|
+
hashed_data[key] = value
|
22
|
+
end
|
23
|
+
@data = OpenStruct.new(hashed_data)
|
24
|
+
else
|
25
|
+
# custom error?
|
26
|
+
return false
|
27
|
+
end
|
28
|
+
@data
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/willow_run.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'willow_run/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "willow_run"
|
8
|
+
spec.version = WillowRun::VERSION
|
9
|
+
spec.authors = ["Kent Gruber"]
|
10
|
+
spec.email = ["kgruber1@emich.edu"]
|
11
|
+
|
12
|
+
spec.summary = %q{Willow Run is a Ruby API to the macOS/OSX airport command providing various wireless utilities.}
|
13
|
+
spec.description = %q{Willow Run is a Ruby API to the macOS/OSX airport command providing various wireless utilities. The airport command manages 802.11 interfaces, can perform wireless broadcast scans ( with xml/plist output support ), set arbitrary channels on the interface, and even generate PSKs from specified pass phrases and SSIDs -- all from the command-line, which I really love. }
|
14
|
+
spec.homepage = "https://github.com/picatz/Willow-Run"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_dependency "plist"
|
21
|
+
spec.add_dependency "packetfu"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: willow_run
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kent Gruber
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-11-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: plist
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: packetfu
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.13'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.13'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
description: 'Willow Run is a Ruby API to the macOS/OSX airport command providing
|
84
|
+
various wireless utilities. The airport command manages 802.11 interfaces, can perform
|
85
|
+
wireless broadcast scans ( with xml/plist output support ), set arbitrary channels
|
86
|
+
on the interface, and even generate PSKs from specified pass phrases and SSIDs --
|
87
|
+
all from the command-line, which I really love. '
|
88
|
+
email:
|
89
|
+
- kgruber1@emich.edu
|
90
|
+
executables: []
|
91
|
+
extensions: []
|
92
|
+
extra_rdoc_files: []
|
93
|
+
files:
|
94
|
+
- ".gitignore"
|
95
|
+
- ".rspec"
|
96
|
+
- ".travis.yml"
|
97
|
+
- CODE_OF_CONDUCT.md
|
98
|
+
- Gemfile
|
99
|
+
- LICENSE.txt
|
100
|
+
- README.md
|
101
|
+
- Rakefile
|
102
|
+
- bin/console
|
103
|
+
- bin/setup
|
104
|
+
- examples/all_ssid_strs.rb
|
105
|
+
- examples/all_ssid_strs_and_bssids.rb
|
106
|
+
- examples/current_connected_ap_data.rb
|
107
|
+
- examples/example.rb
|
108
|
+
- examples/first_ap_data.rb
|
109
|
+
- examples/simple_sniffer.rb
|
110
|
+
- lib/willow_run.rb
|
111
|
+
- lib/willow_run/access_point.rb
|
112
|
+
- lib/willow_run/airport.rb
|
113
|
+
- lib/willow_run/errors.rb
|
114
|
+
- lib/willow_run/generate_psk.rb
|
115
|
+
- lib/willow_run/parser.rb
|
116
|
+
- lib/willow_run/scanner.rb
|
117
|
+
- lib/willow_run/sniffer.rb
|
118
|
+
- lib/willow_run/status.rb
|
119
|
+
- lib/willow_run/version.rb
|
120
|
+
- willow_run.gemspec
|
121
|
+
homepage: https://github.com/picatz/Willow-Run
|
122
|
+
licenses:
|
123
|
+
- MIT
|
124
|
+
metadata: {}
|
125
|
+
post_install_message:
|
126
|
+
rdoc_options: []
|
127
|
+
require_paths:
|
128
|
+
- lib
|
129
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
requirements: []
|
140
|
+
rubyforge_project:
|
141
|
+
rubygems_version: 2.5.2
|
142
|
+
signing_key:
|
143
|
+
specification_version: 4
|
144
|
+
summary: Willow Run is a Ruby API to the macOS/OSX airport command providing various
|
145
|
+
wireless utilities.
|
146
|
+
test_files: []
|