tor2 0.1.2
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/AUTHORS +1 -0
- data/CREDITS +0 -0
- data/README.md +108 -0
- data/UNLICENSE +24 -0
- data/VERSION +1 -0
- data/lib/tor.rb +89 -0
- data/lib/tor/config.rb +103 -0
- data/lib/tor/control.rb +310 -0
- data/lib/tor/dnsel.rb +148 -0
- data/lib/tor/version.rb +22 -0
- metadata +82 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f03a9d3b85ee79e8662979fb5dd55d53a14e777e
|
4
|
+
data.tar.gz: 6d3864c849f7c13be6ed17b1bd326d2c4597eac8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cc744f5aa9b4b0ba45e445df1bb592e24d02f0cf16e6fb84f6b00bd8116b48cba0752fb1e68bae038d5b172aa52e50005912bae664ad27668f0ca11259bd889f
|
7
|
+
data.tar.gz: b57b310df5a8d18980ab29dd87d5042b1fad8b50a23e7a2a670c7bcd37a54917c56b123b6d2b1487ba77f9e0be9e886a39220d1e705e9532bdffef46b4e5fdd6
|
data/AUTHORS
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
* Arto Bendiken <arto.bendiken@gmail.com>
|
data/CREDITS
ADDED
File without changes
|
data/README.md
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
Tor.rb: Onion Routing for Ruby (tor2)
|
2
|
+
=====================================
|
3
|
+
|
4
|
+
This is a Ruby library for interacting with the [Tor][] anonymity network.
|
5
|
+
|
6
|
+
**/ ! \ This repository were created to keep an updated version of this lib on the gem store. I am not the author or the maintainer even if PR are welcomed. The original project can be found at <https://github.com/dryruby/tor.rb> (previously <http://github.com/bendiken/tor-ruby>).**
|
7
|
+
|
8
|
+
The name of this alternative gem is `tor2` to avoid confusion with the original.
|
9
|
+
|
10
|
+
Features
|
11
|
+
--------
|
12
|
+
|
13
|
+
* Supports checking whether Tor is installed in the user's current `PATH`,
|
14
|
+
and if it is, returning the version number.
|
15
|
+
* Supports parsing Tor configuration files and looking up the values of
|
16
|
+
particular options.
|
17
|
+
* Supports querying and controlling a locally-running Tor process using the
|
18
|
+
[Tor Control Protocol (TC)][TC] over a socket connection.
|
19
|
+
* Supports querying the [Tor DNS Exit List (DNSEL)][TorDNSEL] to determine
|
20
|
+
whether a particular host is a Tor exit node or not.
|
21
|
+
* Compatible with Ruby 1.8.7+, Ruby 1.9.x, and JRuby 1.4/1.5.
|
22
|
+
|
23
|
+
Examples
|
24
|
+
--------
|
25
|
+
|
26
|
+
require 'rubygems'
|
27
|
+
require 'tor2'
|
28
|
+
|
29
|
+
### Checking whether Tor is installed and which version it is
|
30
|
+
|
31
|
+
Tor.available? #=> true
|
32
|
+
Tor.version #=> "0.2.1.25"
|
33
|
+
|
34
|
+
### Parsing the Tor configuration file (1)
|
35
|
+
|
36
|
+
torrc = Tor::Config.load("/etc/tor/torrc")
|
37
|
+
|
38
|
+
### Parsing the Tor configuration file (2)
|
39
|
+
|
40
|
+
Tor::Config.open("/etc/tor/torrc") do |torrc|
|
41
|
+
puts "Tor SOCKS port: #{torrc['SocksPort']}"
|
42
|
+
puts "Tor control port: #{torrc['ControlPort']}"
|
43
|
+
puts "Tor exit policy:"
|
44
|
+
torrc.each('ExitPolicy') do |key, value|
|
45
|
+
puts " #{value}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
### Communicating with a running Tor process
|
50
|
+
|
51
|
+
Tor::Controller.connect(:port => 9051) do |tor|
|
52
|
+
puts "Tor version: #{tor.version}"
|
53
|
+
puts "Tor config file: #{tor.config_file}"
|
54
|
+
end
|
55
|
+
|
56
|
+
### Checking whether a particular host is a Tor exit node
|
57
|
+
|
58
|
+
Tor::DNSEL.include?("208.75.57.100") #=> true
|
59
|
+
Tor::DNSEL.include?("1.2.3.4") #=> false
|
60
|
+
|
61
|
+
Documentation
|
62
|
+
-------------
|
63
|
+
|
64
|
+
* <http://cypherpunk.rubyforge.org/tor/>
|
65
|
+
|
66
|
+
Dependencies
|
67
|
+
------------
|
68
|
+
|
69
|
+
* [Ruby](http://ruby-lang.org/) (>= 1.8.7) or (>= 1.8.1 with [Backports][])
|
70
|
+
* [Tor](https://www.torproject.org/download.html.en) (>= 0.2.1)
|
71
|
+
|
72
|
+
Installation
|
73
|
+
------------
|
74
|
+
|
75
|
+
The recommended installation method is via [RubyGems](http://rubygems.org/).
|
76
|
+
To install the latest official release of Tor.rb, do:
|
77
|
+
|
78
|
+
% [sudo] gem install tor2 # Ruby 1.8.7+ or 1.9.x
|
79
|
+
% [sudo] gem install backports tor2 # Ruby 1.8.1+
|
80
|
+
|
81
|
+
Download
|
82
|
+
--------
|
83
|
+
|
84
|
+
To get a local working copy of the development repository, do:
|
85
|
+
|
86
|
+
% git clone git://github.com/alexmili/tor.rb.git
|
87
|
+
|
88
|
+
Alternatively, you can download the latest development version as a tarball
|
89
|
+
as follows:
|
90
|
+
|
91
|
+
% wget http://github.com/alexmili/tor.rb/tarball/master
|
92
|
+
|
93
|
+
Author
|
94
|
+
------
|
95
|
+
|
96
|
+
* [Arto Bendiken](mailto:arto.bendiken@gmail.com) - <http://ar.to/>
|
97
|
+
|
98
|
+
License
|
99
|
+
-------
|
100
|
+
|
101
|
+
Tor.rb is free and unencumbered public domain software. For more
|
102
|
+
information, see <http://unlicense.org/> or the accompanying UNLICENSE file.
|
103
|
+
|
104
|
+
[Tor]: https://www.torproject.org/
|
105
|
+
[TorDNSEL]: https://www.torproject.org/tordnsel/
|
106
|
+
[TC]: http://gitweb.torproject.org/tor.git?a=blob_plain;hb=HEAD;f=doc/spec/control-spec.txt
|
107
|
+
[OR]: http://en.wikipedia.org/wiki/Onion_routing
|
108
|
+
[Backports]: http://rubygems.org/gems/backports
|
data/UNLICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
2
|
+
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
4
|
+
distribute this software, either in source code form or as a compiled
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
6
|
+
means.
|
7
|
+
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
9
|
+
of this software dedicate any and all copyright interest in the
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
11
|
+
of the public at large and to the detriment of our heirs and
|
12
|
+
successors. We intend this dedication to be an overt act of
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
14
|
+
software under copyright law.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
For more information, please refer to <http://unlicense.org/>
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.2
|
data/lib/tor.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
if RUBY_VERSION < '1.8.7'
|
4
|
+
# @see http://rubygems.org/gems/backports
|
5
|
+
begin
|
6
|
+
require 'backports/1.8.7'
|
7
|
+
rescue LoadError
|
8
|
+
begin
|
9
|
+
require 'rubygems'
|
10
|
+
require 'backports/1.8.7'
|
11
|
+
rescue LoadError
|
12
|
+
abort "Tor.rb requires Ruby 1.8.7 or the Backports gem (hint: `gem install backports')."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# @see https://www.torproject.org/
|
19
|
+
module Tor
|
20
|
+
require_relative 'tor/config'
|
21
|
+
require_relative 'tor/control'
|
22
|
+
require_relative 'tor/dnsel'
|
23
|
+
require_relative 'tor/version'
|
24
|
+
|
25
|
+
##
|
26
|
+
# Returns `true` if the Tor process is running locally, `false` otherwise.
|
27
|
+
#
|
28
|
+
# This works by attempting to establish a Tor Control Protocol (TC)
|
29
|
+
# connection to the standard control port 9051 on `localhost`. If Tor
|
30
|
+
# hasn't been configured with the `ControlPort 9051` option, this will
|
31
|
+
# return `false`.
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# Tor.running? #=> false
|
35
|
+
#
|
36
|
+
# @return [Boolean]
|
37
|
+
# @since 0.1.2
|
38
|
+
def self.running?
|
39
|
+
begin
|
40
|
+
Tor::Controller.new.quit
|
41
|
+
true
|
42
|
+
rescue Errno::ECONNREFUSED
|
43
|
+
false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Returns `true` if Tor is available, `false` otherwise.
|
49
|
+
#
|
50
|
+
# @example
|
51
|
+
# Tor.available? #=> true
|
52
|
+
#
|
53
|
+
# @return [Boolean]
|
54
|
+
def self.available?
|
55
|
+
!!program_path
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Returns the Tor version number, or `nil` if Tor is not available.
|
60
|
+
#
|
61
|
+
# @example
|
62
|
+
# Tor.version #=> "0.2.1.25"
|
63
|
+
#
|
64
|
+
# @return [String]
|
65
|
+
def self.version
|
66
|
+
if available? && `#{program_path} --version` =~ /Tor v(\d+)\.(\d+)\.(\d+)\.(\d+)/
|
67
|
+
[$1, $2, $3, $4].join('.')
|
68
|
+
elsif available? && `#{program_path} --version` =~ /Tor version (\d+)\.(\d+)\.(\d+)\.(\d+)/
|
69
|
+
[$1, $2, $3, $4].join('.')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Returns the path to the `tor` executable, or `nil` if the program could
|
75
|
+
# not be found in the user's current `PATH` environment.
|
76
|
+
#
|
77
|
+
# @example
|
78
|
+
# Tor.program_path #=> "/opt/local/bin/tor"
|
79
|
+
#
|
80
|
+
# @param [String, #to_s] program_name
|
81
|
+
# @return [String]
|
82
|
+
def self.program_path(program_name = :tor)
|
83
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
84
|
+
program_path = File.join(path, program_name.to_s)
|
85
|
+
return program_path if File.executable?(program_path)
|
86
|
+
end
|
87
|
+
return nil
|
88
|
+
end
|
89
|
+
end
|
data/lib/tor/config.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
module Tor
|
2
|
+
##
|
3
|
+
# Tor configuration.
|
4
|
+
#
|
5
|
+
# @example Parsing a Tor configuration file (1)
|
6
|
+
# torrc = Tor::Config.load("/etc/tor/torrc")
|
7
|
+
#
|
8
|
+
# @example Parsing a Tor configuration file (2)
|
9
|
+
# Tor::Config.open("/etc/tor/torrc") do |torrc|
|
10
|
+
# puts "Tor SOCKS port: #{torrc['SocksPort']}"
|
11
|
+
# puts "Tor control port: #{torrc['ControlPort']}"
|
12
|
+
# puts "Tor exit policy:"
|
13
|
+
# torrc.each('ExitPolicy') do |key, value|
|
14
|
+
# puts " #{value}"
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# @see https://www.torproject.org/tor-manual.html.en
|
19
|
+
# @since 0.1.2
|
20
|
+
class Config
|
21
|
+
CONFDIR = '/etc/tor' unless defined?(CONFDIR)
|
22
|
+
|
23
|
+
##
|
24
|
+
# Opens a Tor configuration file.
|
25
|
+
#
|
26
|
+
# @param [String, #to_s] filename
|
27
|
+
# @param [Hash{Symbol => Object}] options
|
28
|
+
# @yield [config]
|
29
|
+
# @yieldparam [Config] config
|
30
|
+
# @return [Config]
|
31
|
+
def self.open(filename, options = {}, &block)
|
32
|
+
if block_given?
|
33
|
+
block.call(self.load(filename, options))
|
34
|
+
else
|
35
|
+
self.load(filename, options)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Loads the configuration options from a Tor configuration file.
|
41
|
+
#
|
42
|
+
# @param [String, #to_s] filename
|
43
|
+
# @param [Hash{Symbol => Object}] options
|
44
|
+
# @return [Config]
|
45
|
+
def self.load(filename, options = {})
|
46
|
+
self.new(options) do |config|
|
47
|
+
File.open(filename.to_s, 'rb') do |file|
|
48
|
+
file.each_line do |line|
|
49
|
+
case line = line.strip.chomp.strip
|
50
|
+
when '' then next # skip empty lines
|
51
|
+
when /^#/ then next # skip comments
|
52
|
+
else line = line.split('#').first.strip
|
53
|
+
end
|
54
|
+
# TODO: support for unquoting and unescaping values
|
55
|
+
config << line.split(/\s+/, 2)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# @param [Hash{Symbol => Object}] options
|
63
|
+
# @yield [config]
|
64
|
+
# @yieldparam [Config] config
|
65
|
+
def initialize(options = {}, &block)
|
66
|
+
@lines, @options = [], options.dup
|
67
|
+
block.call(self) if block_given?
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Appends a new configuration option.
|
72
|
+
#
|
73
|
+
# @param [Array(String, String)]
|
74
|
+
# @return [Config]
|
75
|
+
def <<(kv)
|
76
|
+
@lines << kv
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Looks up the last value of a particular configuration option.
|
82
|
+
#
|
83
|
+
# @param [String, Regexp] key
|
84
|
+
# @return [String]
|
85
|
+
def [](key)
|
86
|
+
values = each(key).map(&:last)
|
87
|
+
values.empty? ? nil : values.last
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# Enumerates configuration options.
|
92
|
+
#
|
93
|
+
# @param [String, Regexp] key
|
94
|
+
# @yield [key, value]
|
95
|
+
# @yieldparam [String] key
|
96
|
+
# @yieldparam [String] value
|
97
|
+
# @return [Enumerator]
|
98
|
+
def each(key = nil, &block)
|
99
|
+
return enum_for(:each, key) unless block_given?
|
100
|
+
key ? @lines.find_all { |k, v| key === k }.each(&block) : @lines.each(&block)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/tor/control.rb
ADDED
@@ -0,0 +1,310 @@
|
|
1
|
+
require 'socket' unless defined?(Socket)
|
2
|
+
|
3
|
+
module Tor
|
4
|
+
##
|
5
|
+
# Tor Control Protocol (TC) client.
|
6
|
+
#
|
7
|
+
# The Tor control protocol is used by other programs (such as frontend
|
8
|
+
# user interfaces) to communicate with a locally running Tor process. It
|
9
|
+
# is not part of the Tor onion routing protocol.
|
10
|
+
#
|
11
|
+
# @example Establishing a controller connection (1)
|
12
|
+
# tor = Tor::Controller.new
|
13
|
+
#
|
14
|
+
# @example Establishing a controller connection (2)
|
15
|
+
# tor = Tor::Controller.new(:host => '127.0.0.1', :port => 9051)
|
16
|
+
#
|
17
|
+
# @example Authenticating the controller connection
|
18
|
+
# tor.authenticate
|
19
|
+
#
|
20
|
+
# @example Obtaining information about the Tor process
|
21
|
+
# tor.version #=> "0.2.1.25"
|
22
|
+
# tor.config_file #=> #<Pathname:/opt/local/etc/tor/torrc>
|
23
|
+
#
|
24
|
+
# @see http://gitweb.torproject.org/tor.git?a=blob_plain;hb=HEAD;f=doc/spec/control-spec.txt
|
25
|
+
# @see http://www.thesprawl.org/memdump/?entry=8
|
26
|
+
# @since 0.1.1
|
27
|
+
class Controller
|
28
|
+
PROTOCOL_VERSION = 1
|
29
|
+
|
30
|
+
##
|
31
|
+
# @param [Hash{Symbol => Object}] options
|
32
|
+
# @option options [String, #to_s] :host ("127.0.0.1")
|
33
|
+
# @option options [Integer, #to_i] :port (9051)
|
34
|
+
# @option options [String, #to_s] :cookie (nil)
|
35
|
+
# @option options [Integer, #to_i] :version (PROTOCOL_VERSION)
|
36
|
+
def self.connect(options = {}, &block)
|
37
|
+
if block_given?
|
38
|
+
result = block.call(tor = self.new(options))
|
39
|
+
tor.quit
|
40
|
+
result
|
41
|
+
else
|
42
|
+
self.new(options)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# @param [Hash{Symbol => Object}] options
|
48
|
+
# @option options [String, #to_s] :host ("127.0.0.1")
|
49
|
+
# @option options [Integer, #to_i] :port (9051)
|
50
|
+
# @option options [String, #to_s] :cookie (nil)
|
51
|
+
# @option options [Integer, #to_i] :version (PROTOCOL_VERSION)
|
52
|
+
def initialize(options = {}, &block)
|
53
|
+
@options = options.dup
|
54
|
+
@host = (@options.delete(:host) || '127.0.0.1').to_s
|
55
|
+
@port = (@options.delete(:port) || 9051).to_i
|
56
|
+
@version = (@options.delete(:version) || PROTOCOL_VERSION).to_i
|
57
|
+
connect
|
58
|
+
if block_given?
|
59
|
+
block.call(self)
|
60
|
+
quit
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
attr_reader :host, :port
|
65
|
+
|
66
|
+
##
|
67
|
+
# Establishes the socket connection to the Tor process.
|
68
|
+
#
|
69
|
+
# @example
|
70
|
+
# tor.close
|
71
|
+
# tor.connect
|
72
|
+
#
|
73
|
+
# @return [void]
|
74
|
+
def connect
|
75
|
+
close
|
76
|
+
@socket = TCPSocket.new(@host, @port)
|
77
|
+
@socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Returns `true` if the controller connection is active.
|
83
|
+
#
|
84
|
+
# @example
|
85
|
+
# tor.connected? #=> true
|
86
|
+
# tor.close
|
87
|
+
# tor.connected? #=> false
|
88
|
+
#
|
89
|
+
# @return [Boolean]
|
90
|
+
def connected?
|
91
|
+
!!@socket
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Closes the socket connection to the Tor process.
|
96
|
+
#
|
97
|
+
# @example
|
98
|
+
# tor.close
|
99
|
+
#
|
100
|
+
# @return [void]
|
101
|
+
def close
|
102
|
+
@socket.close if @socket
|
103
|
+
@socket = nil
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# Tells the Tor process to hang up on this controller connection.
|
109
|
+
#
|
110
|
+
# This command can be used before authenticating.
|
111
|
+
#
|
112
|
+
# @example
|
113
|
+
# C: QUIT
|
114
|
+
# S: 250 closing connection
|
115
|
+
# ^D
|
116
|
+
#
|
117
|
+
# @example
|
118
|
+
# tor.quit
|
119
|
+
#
|
120
|
+
# @return [void]
|
121
|
+
def quit
|
122
|
+
send_line('QUIT')
|
123
|
+
reply = read_reply
|
124
|
+
close
|
125
|
+
reply
|
126
|
+
end
|
127
|
+
|
128
|
+
##
|
129
|
+
# Returns information about the authentication method required by the
|
130
|
+
# Tor process.
|
131
|
+
#
|
132
|
+
# This command may be used before authenticating.
|
133
|
+
#
|
134
|
+
# @example
|
135
|
+
# C: PROTOCOLINFO
|
136
|
+
# S: 250-PROTOCOLINFO 1
|
137
|
+
# S: 250-AUTH METHODS=NULL
|
138
|
+
# S: 250-VERSION Tor="0.2.1.25"
|
139
|
+
# S: 250 OK
|
140
|
+
#
|
141
|
+
# @example
|
142
|
+
# tor.authentication_method #=> nil
|
143
|
+
# tor.authentication_method #=> :hashedpassword
|
144
|
+
# tor.authentication_method #=> :cookie
|
145
|
+
#
|
146
|
+
# @return [Symbol]
|
147
|
+
# @since 0.1.2
|
148
|
+
def authentication_method
|
149
|
+
@authentication_method ||= begin
|
150
|
+
method = nil
|
151
|
+
send_line('PROTOCOLINFO')
|
152
|
+
loop do
|
153
|
+
# TODO: support for reading multiple authentication methods
|
154
|
+
case reply = read_reply
|
155
|
+
when /^250-AUTH METHODS=(\w*)/
|
156
|
+
method = $1.strip.downcase.to_sym
|
157
|
+
method = method.eql?(:null) ? nil : method
|
158
|
+
when /^250-/ then next
|
159
|
+
when '250 OK' then break
|
160
|
+
end
|
161
|
+
end
|
162
|
+
method
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
##
|
167
|
+
# Returns `true` if the controller connection has been authenticated.
|
168
|
+
#
|
169
|
+
# @example
|
170
|
+
# tor.authenticated? #=> false
|
171
|
+
# tor.authenticate
|
172
|
+
# tor.authenticated? #=> true
|
173
|
+
#
|
174
|
+
# @return [Boolean]
|
175
|
+
def authenticated?
|
176
|
+
@authenticated || false
|
177
|
+
end
|
178
|
+
|
179
|
+
##
|
180
|
+
# Authenticates the controller connection.
|
181
|
+
#
|
182
|
+
# @example
|
183
|
+
# C: AUTHENTICATE
|
184
|
+
# S: 250 OK
|
185
|
+
#
|
186
|
+
# @example
|
187
|
+
# tor.authenticate
|
188
|
+
#
|
189
|
+
# @return [void]
|
190
|
+
# @raise [AuthenticationError] if authentication failed
|
191
|
+
def authenticate(cookie = nil)
|
192
|
+
cookie ||= @options[:cookie]
|
193
|
+
send(:send_line, cookie ? "AUTHENTICATE \"#{cookie}\"" : "AUTHENTICATE")
|
194
|
+
case reply = read_reply
|
195
|
+
when '250 OK' then @authenticated = true
|
196
|
+
else raise AuthenticationError.new(reply)
|
197
|
+
end
|
198
|
+
self
|
199
|
+
end
|
200
|
+
|
201
|
+
##
|
202
|
+
# Returns the version number of the Tor process.
|
203
|
+
#
|
204
|
+
# @example
|
205
|
+
# C: GETINFO version
|
206
|
+
# S: 250-version=0.2.1.25
|
207
|
+
# S: 250 OK
|
208
|
+
#
|
209
|
+
# @example
|
210
|
+
# tor.version #=> "0.2.1.25"
|
211
|
+
#
|
212
|
+
# @return [String]
|
213
|
+
def version
|
214
|
+
send_command(:getinfo, 'version')
|
215
|
+
reply = read_reply.split('=').last
|
216
|
+
read_reply # skip "250 OK"
|
217
|
+
reply
|
218
|
+
end
|
219
|
+
|
220
|
+
##
|
221
|
+
# Returns the path to the Tor configuration file.
|
222
|
+
#
|
223
|
+
# @example
|
224
|
+
# C: GETINFO config-file
|
225
|
+
# S: 250-config-file=/opt/local/etc/tor/torrc
|
226
|
+
# S: 250 OK
|
227
|
+
#
|
228
|
+
# @example
|
229
|
+
# tor.config_file #=> #<Pathname:/opt/local/etc/tor/torrc>
|
230
|
+
#
|
231
|
+
# @return [Pathname]
|
232
|
+
def config_file
|
233
|
+
send_command(:getinfo, 'config-file')
|
234
|
+
reply = read_reply.split('=').last
|
235
|
+
read_reply # skip "250 OK"
|
236
|
+
Pathname(reply)
|
237
|
+
end
|
238
|
+
|
239
|
+
##
|
240
|
+
# Returns the current (in-memory) Tor configuration.
|
241
|
+
# Response is terminated with a "."
|
242
|
+
#
|
243
|
+
# @example
|
244
|
+
# C: GETINFO config-text
|
245
|
+
# S: 250+config-text=
|
246
|
+
# S: ControlPort 9051
|
247
|
+
# S: RunAsDaemon 1
|
248
|
+
# S: .
|
249
|
+
def config_text
|
250
|
+
send_command(:getinfo, 'config-text')
|
251
|
+
reply = ""
|
252
|
+
read_reply # skip "250+config-text="
|
253
|
+
while line = read_reply
|
254
|
+
break unless line != "."
|
255
|
+
reply.concat(line + "\n")
|
256
|
+
end
|
257
|
+
read_reply # skip "250 OK"
|
258
|
+
return reply
|
259
|
+
end
|
260
|
+
|
261
|
+
##
|
262
|
+
# Send a signal to the server
|
263
|
+
#
|
264
|
+
# @example
|
265
|
+
# tor.signal("newnym")
|
266
|
+
#
|
267
|
+
# @return [String]
|
268
|
+
def signal(name)
|
269
|
+
send_command(:signal, name)
|
270
|
+
read_reply
|
271
|
+
end
|
272
|
+
|
273
|
+
protected
|
274
|
+
|
275
|
+
##
|
276
|
+
# Sends a command line over the socket.
|
277
|
+
#
|
278
|
+
# @param [Symbol, #to_s] command
|
279
|
+
# @param [Array<String>] args
|
280
|
+
# @return [void]
|
281
|
+
def send_command(command, *args)
|
282
|
+
authenticate unless authenticated?
|
283
|
+
send_line(["#{command.to_s.upcase}", *args].join(' '))
|
284
|
+
end
|
285
|
+
|
286
|
+
##
|
287
|
+
# Sends a text line over the socket.
|
288
|
+
#
|
289
|
+
# @param [String, #to_s] line
|
290
|
+
# @return [void]
|
291
|
+
def send_line(line)
|
292
|
+
@socket.write(line.to_s + "\r\n")
|
293
|
+
@socket.flush
|
294
|
+
end
|
295
|
+
|
296
|
+
##
|
297
|
+
# Reads a reply line from the socket.
|
298
|
+
#
|
299
|
+
# @return [String]
|
300
|
+
def read_reply
|
301
|
+
@socket.readline.chomp
|
302
|
+
end
|
303
|
+
|
304
|
+
##
|
305
|
+
# Used to signal an authentication error.
|
306
|
+
#
|
307
|
+
# @see Tor::Controller#authenticate
|
308
|
+
class AuthenticationError < StandardError; end
|
309
|
+
end
|
310
|
+
end
|
data/lib/tor/dnsel.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'resolv' unless defined?(Resolv)
|
2
|
+
|
3
|
+
module Tor
|
4
|
+
##
|
5
|
+
# Tor DNS Exit List (DNSEL) client.
|
6
|
+
#
|
7
|
+
# Unless the target IP address and port are explicitly specified, the
|
8
|
+
# query will be performed using a target IP address of "8.8.8.8" and a
|
9
|
+
# target port of 53. These correspond to the DNS protocol port on one of
|
10
|
+
# the [Google Public DNS](http://code.google.com/speed/public-dns/)
|
11
|
+
# servers, and they are guaranteed to be reachable from Tor's default exit
|
12
|
+
# policy.
|
13
|
+
#
|
14
|
+
# @example Checking source IP addresses
|
15
|
+
# Tor::DNSEL.include?("208.75.57.100") #=> true
|
16
|
+
# Tor::DNSEL.include?("1.2.3.4") #=> false
|
17
|
+
#
|
18
|
+
# @example Checking source hostnames
|
19
|
+
# Tor::DNSEL.include?("ennui.lostinthenoise.net") #=> true
|
20
|
+
# Tor::DNSEL.include?("myhost.example.org") #=> false
|
21
|
+
#
|
22
|
+
# @example Specifying an explicit target port
|
23
|
+
# Tor::DNSEL.include?("208.75.57.100", :port => 80) #=> true
|
24
|
+
# Tor::DNSEL.include?("208.75.57.100", :port => 25) #=> false
|
25
|
+
#
|
26
|
+
# @example Specifying an explicit target IP address and port
|
27
|
+
# Tor::DNSEL.include?(source_addr, :addr => target_addr, :port => target_port)
|
28
|
+
# Tor::DNSEL.include?("208.75.57.100", :addr => myip, :port => myport)
|
29
|
+
#
|
30
|
+
# @example Using from a Rack application
|
31
|
+
# Tor::DNSEL.include?(env['REMOTE_ADDR'] || env['REMOTE_HOST'], {
|
32
|
+
# :addr => env['SERVER_NAME'],
|
33
|
+
# :port => env['SERVER_PORT'],
|
34
|
+
# })
|
35
|
+
#
|
36
|
+
# @see https://www.torproject.org/tordnsel/
|
37
|
+
# @see https://trac.torproject.org/projects/tor/wiki/TheOnionRouter/TorDNSExitList
|
38
|
+
# @see http://gitweb.torproject.org/tor.git?a=blob_plain;hb=HEAD;f=doc/contrib/torel-design.txt
|
39
|
+
module DNSEL
|
40
|
+
RESOLVER = Resolv::DefaultResolver unless defined?(RESOLVER)
|
41
|
+
TARGET_ADDR = '8.8.8.8'.freeze unless defined?(TARGET_ADDR) # Google Public DNS
|
42
|
+
TARGET_PORT = 53 unless defined?(TARGET_PORT) # DNS
|
43
|
+
DNS_SUFFIX = 'ip-port.exitlist.torproject.org'.freeze
|
44
|
+
|
45
|
+
##
|
46
|
+
# Returns `true` if the Tor DNSEL includes `host`, `false` otherwise.
|
47
|
+
#
|
48
|
+
# If the DNS server is unreachable or the DNS query times out, returns
|
49
|
+
# `nil` to indicate that we don't have a definitive answer one way or
|
50
|
+
# another.
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# Tor::DNSEL.include?("208.75.57.100") #=> true
|
54
|
+
# Tor::DNSEL.include?("1.2.3.4") #=> false
|
55
|
+
#
|
56
|
+
# @param [String, #to_s] host
|
57
|
+
# @param [Hash{Symbol => Object}] options
|
58
|
+
# @option options [String, #to_s] :addr ("8.8.8.8")
|
59
|
+
# @option options [Integer, #to_i] :port (53)
|
60
|
+
# @return [Boolean]
|
61
|
+
def self.include?(host, options = {})
|
62
|
+
begin
|
63
|
+
query(host, options) == '127.0.0.2'
|
64
|
+
rescue Resolv::ResolvError # NXDOMAIN
|
65
|
+
false
|
66
|
+
rescue Resolv::ResolvTimeout
|
67
|
+
nil
|
68
|
+
rescue Errno::EHOSTUNREACH
|
69
|
+
nil
|
70
|
+
rescue Errno::EADDRNOTAVAIL
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Queries the Tor DNSEL for `host`, returning "172.0.0.2" if it is an
|
77
|
+
# exit node and raising a `Resolv::ResolvError` if it isn't.
|
78
|
+
#
|
79
|
+
# @example
|
80
|
+
# Tor::DNSEL.query("208.75.57.100") #=> "127.0.0.2"
|
81
|
+
# Tor::DNSEL.query("1.2.3.4") #=> Resolv::ResolvError
|
82
|
+
#
|
83
|
+
# @param [String, #to_s] host
|
84
|
+
# @param [Hash{Symbol => Object}] options
|
85
|
+
# @option options [String, #to_s] :addr ("8.8.8.8")
|
86
|
+
# @option options [Integer, #to_i] :port (53)
|
87
|
+
# @return [String]
|
88
|
+
# @raise [Resolv::ResolvError] for an NXDOMAIN response
|
89
|
+
def self.query(host, options = {})
|
90
|
+
getaddress(dnsname(host, options))
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# Returns the DNS name used for Tor DNSEL queries of `host`.
|
95
|
+
#
|
96
|
+
# @example
|
97
|
+
# Tor::DNSEL.dnsname("1.2.3.4") #=> "4.3.2.1.53.8.8.8.8.ip-port.exitlist.torproject.org"
|
98
|
+
#
|
99
|
+
# @param [String, #to_s] host
|
100
|
+
# @param [Hash{Symbol => Object}] options
|
101
|
+
# @option options [String, #to_s] :addr ("8.8.8.8")
|
102
|
+
# @option options [Integer, #to_i] :port (53)
|
103
|
+
# @return [String]
|
104
|
+
def self.dnsname(host, options = {})
|
105
|
+
source_addr = getaddress(host, true)
|
106
|
+
target_addr = getaddress(options[:addr] || TARGET_ADDR, true)
|
107
|
+
target_port = options[:port] || TARGET_PORT
|
108
|
+
[source_addr, target_port, target_addr, DNS_SUFFIX].join('.')
|
109
|
+
end
|
110
|
+
class << self; alias_method :hostname, :dnsname; end
|
111
|
+
|
112
|
+
protected
|
113
|
+
|
114
|
+
##
|
115
|
+
# Resolves `host` into an IPv4 address using Ruby's default resolver.
|
116
|
+
#
|
117
|
+
# Optionally returns the IPv4 address with its octet order reversed.
|
118
|
+
#
|
119
|
+
# @example
|
120
|
+
# Tor::DNSEL.getaddress("ruby-lang.org") #=> "221.186.184.68"
|
121
|
+
# Tor::DNSEL.getaddress("1.2.3.4") #=> "1.2.3.4"
|
122
|
+
# Tor::DNSEL.getaddress("1.2.3.4", true) #=> "4.3.2.1"
|
123
|
+
#
|
124
|
+
# @param [String, #to_s] host
|
125
|
+
# @param [Boolean] reversed
|
126
|
+
# @return [String]
|
127
|
+
def self.getaddress(host, reversed = false)
|
128
|
+
host = case host.to_s
|
129
|
+
when Resolv::IPv6::Regex
|
130
|
+
raise ArgumentError.new("not an IPv4 address: #{host}")
|
131
|
+
when Resolv::IPv4::Regex
|
132
|
+
host.to_s
|
133
|
+
else
|
134
|
+
begin
|
135
|
+
RESOLVER.each_address(host.to_s) do |addr|
|
136
|
+
return addr.to_s if addr.to_s =~ Resolv::IPv4::Regex
|
137
|
+
end
|
138
|
+
raise Resolv::ResolvError.new("no address for #{host}")
|
139
|
+
rescue NoMethodError
|
140
|
+
# This is a workaround for Ruby bug #2614:
|
141
|
+
# @see http://redmine.ruby-lang.org/issues/show/2614
|
142
|
+
raise Resolv::ResolvError.new("no address for #{host}")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
reversed ? host.split('.').reverse.join('.') : host
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
data/lib/tor/version.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Tor
|
2
|
+
module VERSION
|
3
|
+
MAJOR = 0
|
4
|
+
MINOR = 1
|
5
|
+
TINY = 2
|
6
|
+
EXTRA = nil
|
7
|
+
|
8
|
+
STRING = [MAJOR, MINOR, TINY, EXTRA].compact.join('.')
|
9
|
+
|
10
|
+
##
|
11
|
+
# @return [String]
|
12
|
+
def self.to_s() STRING end
|
13
|
+
|
14
|
+
##
|
15
|
+
# @return [String]
|
16
|
+
def self.to_str() STRING end
|
17
|
+
|
18
|
+
##
|
19
|
+
# @return [Array(Integer, Integer, Integer)]
|
20
|
+
def self.to_a() [MAJOR, MINOR, TINY] end
|
21
|
+
end
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tor2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Arto Bendiken
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-01-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: yard
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.6.0
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.6.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.3.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.3.0
|
41
|
+
description: Tor.rb is a Ruby library for interacting with the Tor anonymity network.
|
42
|
+
email: or-talk@seul.org
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- AUTHORS
|
48
|
+
- CREDITS
|
49
|
+
- README.md
|
50
|
+
- UNLICENSE
|
51
|
+
- VERSION
|
52
|
+
- lib/tor/config.rb
|
53
|
+
- lib/tor/control.rb
|
54
|
+
- lib/tor/dnsel.rb
|
55
|
+
- lib/tor/version.rb
|
56
|
+
- lib/tor.rb
|
57
|
+
homepage: https://github.com/AlexMili/tor.rb
|
58
|
+
licenses:
|
59
|
+
- Public Domain
|
60
|
+
metadata: {}
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.8.1
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
requirements:
|
76
|
+
- Tor (>= 0.2.1.25)
|
77
|
+
rubyforge_project: cypherpunk
|
78
|
+
rubygems_version: 2.0.14.1
|
79
|
+
signing_key:
|
80
|
+
specification_version: 4
|
81
|
+
summary: Onion routing for Ruby.
|
82
|
+
test_files: []
|