tormanager 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 04505aabbc5658c80ee5124866e5fa939a1d42d1
4
+ data.tar.gz: 7798de7e4ac6228cc2afd6e2bf68873d94fcbc82
5
+ SHA512:
6
+ metadata.gz: cf53e41f6cab0da777462c69a526e63de3dc5802fac1b07b82425d5ba66cadcd3f62b115fb948354e4dcbd9417aa15d5eb8410686558a86a9cfe381191c87726
7
+ data.tar.gz: e7828a3ae09c95e22ee3f04dbb5abcf71efc7264a024da5f6ec441e3f3f35b40cd58c0973b8f1022d784b2898cc4e029c327651cb343344edc00e26ad350ab90
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /.idea
11
+
12
+ # rspec failure tracking
13
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.14.6
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tormanager.gemspec
4
+ gemspec
5
+
6
+ gem 'tor', :git => 'https://github.com/bendiken/tor-ruby.git', :ref => '08e589d17196a5dc640e7b38cb1acc5b4c5ced05'
7
+ #gem 'tor', :git => 'https://github.com/dryruby/tor.rb.git'
8
+ group :development, :test do
9
+ gem "rb-fsevent", :require => false if RUBY_PLATFORM =~ /darwin/i
10
+ gem "guard-rspec"
11
+ end
data/Guardfile ADDED
@@ -0,0 +1,18 @@
1
+ guard :rspec, cmd: 'bundle exec rspec' do
2
+ require "guard/rspec/dsl"
3
+ dsl = Guard::RSpec::Dsl.new(self)
4
+
5
+ # RSpec files
6
+ rspec = dsl.rspec
7
+ watch(rspec.spec_helper) { rspec.spec_dir }
8
+ watch(rspec.spec_support) { rspec.spec_dir }
9
+ watch(rspec.spec_files)
10
+
11
+ # Ruby files
12
+ ruby = dsl.ruby
13
+ dsl.watch_spec_files_for(ruby.lib_files)
14
+
15
+ #watch(%r{^spec/.+_spec\.rb$})
16
+ #watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
17
+ #watch('spec/spec_helper.rb') { "spec" }
18
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 joshweir
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,195 @@
1
+ # Tor Manager
2
+
3
+ Ruby gem that provides a Tor interface with functionality:
4
+
5
+ - Start and stop and monitor a Tor process.
6
+ The Tor Process is monitored using [Eye](https://github.com/kostya/eye).
7
+ - Retrieve the current Tor IP address and get new ip address upon request.
8
+ - Proxy web client requests through Tor.
9
+
10
+ ## Installation
11
+
12
+ Install Tor:
13
+
14
+ `sudo apt-get install tor`
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ ```ruby
19
+ gem 'tormanager'
20
+ ```
21
+
22
+ And then execute:
23
+
24
+ $ bundle
25
+
26
+ Or install it yourself as:
27
+
28
+ $ gem install tormanager
29
+
30
+ ## Usage
31
+
32
+ ### Tor Process
33
+
34
+ This section shows how to start, stop and manage Tor processes.
35
+
36
+ Start a Tor process with default settings:
37
+
38
+ tor_process = TorManager::TorProcess.new
39
+ tor_process.start
40
+
41
+ #you can get the control password for the Tor process if you want:
42
+ tor_process.settings[:control_password]
43
+
44
+ By default, the Tor process will use using port `9050` and control port `50500` and
45
+ will be spawned using [Eye](https://github.com/kostya/eye) ensuring that the process remains in a healthy state.
46
+ Eye will generate a config file based on the default [template](https://github.com/joshweir/tormanager/blob/master/lib/tormanager/eye/tor.template.eye.rb).
47
+ Eye will invoke the Tor process with command which looks like this:
48
+
49
+ tor --SocksPort 9050 --ControlPort 50500 --CookieAuthentication 0 --HashedControlPassword "<hashedpassword from the :control_password>" --NewCircuitPeriod 60
50
+
51
+ Eye will restart the Tor process if a cpu percentage exceeds 10% for 3 consecutive readings (checked every 30 seconds). Eye will
52
+ restart the Tor process if its memory exceeds 200MB for 3 consecutive readings (checked every 60 seconds). By default, will do minimal logging and
53
+ will create a random `:control_password` which is used to generate the `HashedControlPassword` used to change the Tor password on request.
54
+ More information can be found in the `TorManager::TorProcess` parameters table below.
55
+
56
+ You can also spawn a Tor process with control over parameters:
57
+
58
+ tor_process =
59
+ TorManager::TorProcess.new tor_port: 9051,
60
+ control_port: 50501,
61
+ pid_dir: '/my/pid/dir',
62
+ log_dir: '/my/log/dir',
63
+ tor_data_dir: '/my/tor/datadir',
64
+ tor_new_circuit_period: 120,
65
+ max_tor_memory_usage_mb: 400,
66
+ max_tor_cpu_percentage: 15,
67
+ control_password: 'mycontrolpass',
68
+ eye_logging: true,
69
+ tor_logging: true
70
+ tor_process.start
71
+
72
+ See the table below for more info.
73
+
74
+ When done with the Tor process, `stop` it:
75
+
76
+ tor_process.stop
77
+
78
+ The following table describes the `TorManager::TorProcess` parameters:
79
+
80
+ | Parameter | Default Value | Description |
81
+ | --- | --- | --- |
82
+ | `:tor_port` | `9050` | The listening port of the Tor process. |
83
+ | `:control_port` | `50500` | The control port of the Tor process. |
84
+ | `:pid_dir` | `/tmp` | Eye will create a pid file in this location which stores the pid of the Tor process. The name of the pid file is of format: `tormanager-tor-<tor_port>-<parent_pid>.pid` **_†_**. |
85
+ | `:log_dir` | `/tmp` | If `:eye_logging` is `true`, Eye will create a log (`tormanager.eye.log`) in this location. If `:tor_logging` is `true`, Tor `stdall` is redirected to log (`tormanager-tor-<tor_port>-<parent_pid>.log` **_†_**) in this location. |
86
+ | `:tor_data_dir` | `nil` | If specified, Tor will use this directory location as the `--DataDirectory`. |
87
+ | `:tor_new_circuit_period` | `60` | In seconds, specifies the `--NewCircuitPeriod` that Tor should use. |
88
+ | `:max_tor_memory_usage_mb` | `200` | In megabytes, Eye will restart the Tor process if its memory exceeds this value for 3 consecutive readings (checked every 60 seconds). |
89
+ | `:max_tor_cpu_percentage` | `10` | Percentage value, Eye will restart the Tor process if it's cpu percentage exceeds this value for 3 consecutive readings (checked every 30 seconds). |
90
+ | `:eye_tor_config_template` | `tormanager/eye/tor.template.eye.rb` | Specify your own eye config template, it is recommended to use the default [template](https://github.com/joshweir/tormanager/blob/master/lib/tormanager/eye/tor.template.eye.rb) as your starting point. |
91
+ | `:control_password` | Randomly generated by default. | By default, will do minimal logging and will create a random `:control_password` which is used to generate the `HashedControlPassword` used to change the Tor password on request. |
92
+ | `:tor_log_switch` | `nil` | If specified, sets the Tor `--Log` switch, for example a value of `notice syslog` will add `--Log "notice syslog"` to the Tor command. |
93
+ | `:eye_logging` | `nil` | If set to `true` will enable Eye logging in the `:log_dir` location, eye log: `tormanager.eye.log` |
94
+ | `:tor_logging` | `nil` | If set to `true`, Tor `stdall` is redirected to log (`tormanager-tor-<tor_port>-<parent_pid>.log` **_†_**) in this location. |
95
+ | `:dont_remove_tor_config` | `nil` | By default, an eye configuration file is generated for the current Tor instance based on the `:eye_tor_config_template` stored in the `:log_dir` with name `tormanager.tor.<tor_port>.<parent_pid>.eye.rb` **_†_**. This generated file is removed when the Tor process is stopped by default. Setting `:dont_remove_tor_config` to `true` will not remove this file. |
96
+
97
+ **_†_** where `<tor_port>` is the `:tor_port` of the Tor process and `<parent_pid>` is the pid of the ruby process spawning the Tor process.
98
+
99
+ To stop any Tor instances that have been previously started by Tor Manager but were not stopped (say in the event of a parent process crash) **_††_**:
100
+
101
+ TorProcess.stop_obsolete_processes
102
+
103
+ Query whether Tor Manager has any Tor processes running on a particular port **_††_**:
104
+
105
+ TorProcess.tor_running_on? port: 9050
106
+
107
+ Query whether Tor Manager has any Tor processes running on a particular port associated to a particular parent ruby pid **_††_**:
108
+
109
+ TorProcess.tor_running_on? port: 9050, parent_pid: 12345
110
+
111
+ **_††_** Note that this command applies only to Tor processes that were started by Tor Manager.
112
+ Tor processes that have been started external to Tor Manager will not be impacted.
113
+
114
+ ### Proxy Through Tor, Query IP Address, Change IP Address
115
+
116
+ Once you have a `TorProcess` started, you can:
117
+
118
+ - [Proxy web client requests through Tor.](#proxy-through-tor)
119
+ - [Query the current Tor endpoint IP address.](#query-ip-address)
120
+ - [Change the Tor endpoint IP address.](#change-ip-address)
121
+
122
+ The remaining examples assume that you have instantiated a `TorProcess` ie:
123
+
124
+ tor_process = TorManager::TorProcess.new
125
+ tor_process.start
126
+
127
+ #### Proxy Through Tor
128
+
129
+ tor_proxy = TorManager::Proxy.new tor_process: tor_process
130
+ tor_proxy.proxy do
131
+ tor_ip = RestClient::Request.execute(
132
+ method: :get,
133
+ url: 'http://bot.whatismyipaddress.com').to_str
134
+ end
135
+ my_ip = RestClient::Request.execute(
136
+ method: :get,
137
+ url: 'http://bot.whatismyipaddress.com').to_str
138
+
139
+ Note that in the above code the `RestClient::Request` returning `tor_ip` is routed through the Tor endpoint because it is yielded
140
+ through the `TorManager::Proxy#proxy` block. The following request returning `my_ip` will not be routed through the Tor endpoint
141
+ hence returning your current IP address.
142
+
143
+ You could route [Capybara](https://github.com/teamcapybara/capybara) requests through the proxy (just make sure you use the `:poltergeist` driver and set the proxy
144
+ to use the Tor socks proxy):
145
+
146
+ Capybara.default_driver = :poltergeist
147
+ Capybara.register_driver :poltergeist do |app|
148
+ Capybara::Poltergeist::Driver.new(app, {
149
+ :phantomjs => Phantomjs.path,
150
+ :phantomjs_options => ["--proxy-type=socks5", "--proxy=127.0.0.1:9050"]
151
+ })
152
+ end
153
+ tor_proxy = TorManager::Proxy.new tor_process: tor_process
154
+ tor_proxy.proxy do
155
+ tor_ip = Capybara.visit('http://bot.whatismyipaddress.com').text
156
+ end
157
+
158
+ #### Query IP Address
159
+
160
+ Query the Tor endpoint for the current IP address:
161
+
162
+ tor_proxy = TorManager::Proxy.new tor_process: tor_process
163
+ tor_ip_control =
164
+ TorManager::IpAddressControl.new tor_process: tor_process,
165
+ tor_proxy: tor_proxy
166
+ tor_ip_control.get_ip
167
+
168
+ When the `TorManager::IpAddressControl#get_ip` method is called, the IP address is stored in instance variable:
169
+
170
+ tor_ip_control.ip
171
+
172
+
173
+ #### Change IP Address
174
+
175
+ Get a new IP address:
176
+
177
+ tor_proxy = TorManager::Proxy.new tor_process: tor_process
178
+ tor_ip_control =
179
+ TorManager::IpAddressControl.new tor_process: tor_process,
180
+ tor_proxy: tor_proxy
181
+ tor_ip_control.get_new_ip
182
+
183
+ When the `TorManager::IpAddressControl#get_new_ip` method is called, the IP address is stored in instance variable:
184
+
185
+ tor_ip_control.ip
186
+
187
+
188
+ ## Contributing
189
+
190
+ Bug reports and pull requests are welcome on GitHub at https://github.com/joshweir/tormanager.
191
+
192
+
193
+ ## License
194
+
195
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "tormanager"
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(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,34 @@
1
+ require 'eye'
2
+
3
+ # Adding application
4
+ Eye.application 'tormanager-tor-9250-1234' do
5
+ # All options inherits down to the config leafs.
6
+ # except `env`, which merging down
7
+
8
+ # uid "user_name" # run app as a user_name (optional) - available only on ruby >= 2.0
9
+ # gid "group_name" # run app as a group_name (optional) - available only on ruby >= 2.0
10
+
11
+ #working_dir File.expand_path(File.join(File.dirname(__FILE__), %w[ processes ]))
12
+ stdall '/tmp/tortrash.log' # stdout,err logs for processes by default
13
+ #env 'APP_ENV' => 'production' # global env for each processes
14
+ trigger :flapping, times: 10, within: 1.minute, retry_in: 10.minutes
15
+ check :cpu, every: 30.seconds, below: 10, times: 3 # global check for all processes
16
+ check :memory, every: 60.seconds, below: 200.megabytes, times: 3
17
+ process :sample1 do
18
+ pid_file '1.pid' # pid_path will be expanded with the working_dir
19
+ start_command "tor --SocksPort 9250 --ControlPort 52500 " +
20
+ "--CookieAuthentication 0 --HashedControlPassword \"16:3E49D6163CCA95F2605B339" +
21
+ "E07F753C8F567DE4200E33FDF4CC6B84E44\" --NewCircuitPeriod " +
22
+ "60 --DataDirectory /tmp/tor_data/9250/ --Log \"notice syslog\""
23
+
24
+ daemonize true
25
+ #stdall 'sample1.log'
26
+ end
27
+
28
+ =begin
29
+ group 'samples' do
30
+ chain grace: 5.seconds # chained start-restart with 5s interval, one by one.
31
+
32
+ end
33
+ =end
34
+ end
@@ -0,0 +1,62 @@
1
+ class Numeric
2
+ # Public: Units of seconds.
3
+ def seconds
4
+ self
5
+ end
6
+
7
+ # Public: Units of seconds.
8
+ alias :second :seconds
9
+
10
+ # Public: Units of minutes (60 seconds).
11
+ def minutes
12
+ self * 60
13
+ end
14
+
15
+ # Public: Units of minutes (60 seconds).
16
+ alias :minute :minutes
17
+
18
+ # Public: Units of hours (3600 seconds).
19
+ def hours
20
+ self * 3600
21
+ end
22
+
23
+ # Public: Units of hours (3600 seconds).
24
+ alias :hour :hours
25
+
26
+ # Public: Units of days (86400 seconds).
27
+ def days
28
+ self * 86400
29
+ end
30
+
31
+ # Public: Units of days (86400 seconds).
32
+ alias :day :days
33
+
34
+ # Units of kilobytes.
35
+ def kilobytes
36
+ self
37
+ end
38
+
39
+ # Units of kilobytes.
40
+ alias :kilobyte :kilobytes
41
+
42
+ # Units of megabytes (1024 kilobytes).
43
+ def megabytes
44
+ self * 1024
45
+ end
46
+
47
+ # Units of megabytes (1024 kilobytes).
48
+ alias :megabyte :megabytes
49
+
50
+ # Units of gigabytes (1,048,576 kilobytes).
51
+ def gigabytes
52
+ self * (1024 ** 2)
53
+ end
54
+
55
+ # Units of gigabytes (1,048,576 kilobytes).
56
+ alias :gigabyte :gigabytes
57
+
58
+ # Units of percent. e.g. 50.percent.
59
+ def percent
60
+ self
61
+ end
62
+ end
@@ -0,0 +1,105 @@
1
+ require 'fileutils'
2
+
3
+ opts = {}
4
+ opts[:parent_id] = ARGV[0]
5
+ opts[:tor_port] = ARGV[1] || 9050
6
+ opts[:control_port] = ARGV[2] || 50500
7
+ opts[:pid_dir] = ARGV[3] || "/tmp"
8
+ opts[:log_dir] = ARGV[4] || "/tmp"
9
+ opts[:tor_data_dir] = ARGV[5] || "/tmp/tor_data/"
10
+ opts[:tor_new_circuit_period] = ARGV[6] || 60
11
+ opts[:max_tor_memory_usage_mb] = ARGV[7] || 200
12
+ opts[:max_tor_cpu_percentage] = ARGV[8] || 10
13
+ opts[:max_tor_memory_usage_times] = [3,5]
14
+ opts[:max_tor_cpu_percentage_times] = [3,5]
15
+
16
+ raise 'parent_id is required' unless opts[:parent_id]
17
+
18
+ module God
19
+ module Behaviors
20
+ class WaitBehavior < Behavior
21
+ attr_accessor :delay
22
+
23
+ def after_start
24
+ sleep delay.to_i if delay.to_i > 0
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ God.watch do |w|
31
+ the_name = "tormanager-tor-#{opts[:tor_port]}-#{opts[:parent_id]}"
32
+ w.name = the_name
33
+ w.start = "tor --SocksPort #{opts[:tor_port]} --ControlPort #{opts[:control_port]} " +
34
+ "--CookieAuthentication 0 --HashedControlPassword \"16:3E49D6163CCA95F2605B339" +
35
+ "E07F753C8F567DE4200E33FDF4CC6B84E44\" --NewCircuitPeriod " +
36
+ "#{opts[:tor_new_circuit_period]} --DataDirectory " +
37
+ File.join(opts[:tor_data_dir], opts[:tor_port]) + " --Log \"notice syslog\""
38
+ #w.pid_file = File.join(opts[:pid_dir], "#{the_name}.pid")
39
+ #w.log = File.join(opts[:log_dir], "#{the_name}.log") if opts[:log_dir].length > 0
40
+ w.keepalive
41
+
42
+ # clean pid files before start if necessary
43
+ w.behavior(:clean_pid_file)
44
+ w.behavior(:wait_behavior) do |b|
45
+ b.delay = 10
46
+ end
47
+
48
+ =begin
49
+ # determine the state on startup
50
+ w.transition(:init, { true => :up, false => :start }) do |on|
51
+ sleep 5
52
+ on.condition(:process_running) do |c|
53
+ c.interval = 5
54
+ c.running = true
55
+ end
56
+ end
57
+
58
+ # determine when process has finished starting
59
+ w.transition([:start, :restart], :up) do |on|
60
+ on.condition(:process_running) do |c|
61
+ c.interval = 20
62
+ c.running = true
63
+ end
64
+
65
+ # failsafe
66
+ on.condition(:tries) do |c|
67
+ c.interval = 20
68
+ c.times = 5
69
+ c.transition = :start
70
+ end
71
+ end
72
+
73
+ # start if process is not running
74
+ #w.transition(:up, :start) do |on|
75
+ # on.condition(:process_exits) do |c|
76
+ # c.interval = 20
77
+ # end
78
+ #end
79
+
80
+ # restart if memory or cpu is too high
81
+ w.transition(:up, :restart) do |on|
82
+ on.condition(:memory_usage) do |c|
83
+ c.interval = 20
84
+ c.above = opts[:max_tor_memory_usage_mb]
85
+ c.times = opts[:max_tor_memory_usage_times]
86
+ end
87
+
88
+ on.condition(:cpu_usage) do |c|
89
+ c.interval = 10
90
+ c.above = opts[:max_tor_cpu_percentage]
91
+ c.times = opts[:max_tor_cpu_percentage_times]
92
+ end
93
+ end
94
+
95
+ # lifecycle
96
+ w.lifecycle do |on|
97
+ on.condition(:flapping) do |c|
98
+ c.to_state = [:start, :restart]
99
+ c.times = 5
100
+ c.within = 5.minute
101
+ c.transition = :unmonitored
102
+ end
103
+ end
104
+ =end
105
+ end
@@ -0,0 +1,27 @@
1
+ require 'eye'
2
+
3
+ if %w(true 1).include?('[[[eye_logging]]]')
4
+ Eye.config do
5
+ logger File.join('[[[log_dir]]]', 'tormanager.eye.log')
6
+ end
7
+ end
8
+
9
+ Eye.application 'tormanager-tor-[[[tor_port]]]-[[[parent_pid]]]' do
10
+ stdall File.join('[[[log_dir]]]', 'tormanager-tor-[[[tor_port]]]-[[[parent_pid]]].log') if %w(true 1).include?('[[[tor_logging]]]')
11
+ trigger :flapping, times: 10, within: 1.minute, retry_in: 10.minutes
12
+ check :cpu, every: 30.seconds, below: [[[max_tor_cpu_percentage]]], times: 3
13
+ check :memory, every: 60.seconds, below: [[[max_tor_memory_usage_mb]]].megabytes, times: 3
14
+ process :tor do
15
+ pid_file File.join('[[[pid_dir]]]', 'tormanager-tor-[[[tor_port]]]-[[[parent_pid]]].pid')
16
+ start_command "tor --SocksPort [[[tor_port]]] --ControlPort [[[control_port]]] " +
17
+ "--CookieAuthentication 0 --HashedControlPassword \"[[[hashed_control_password]]]\" --NewCircuitPeriod " +
18
+ "[[[tor_new_circuit_period]]] " +
19
+ ('[[[tor_data_dir]]]'.length > 0 ?
20
+ "--DataDirectory #{File.join('[[[tor_data_dir]]]',
21
+ '[[[tor_port]]]')} " :
22
+ "") +
23
+ ('[[[tor_log_switch]]]'.length > 0 ?
24
+ "--Log \"[[[tor_log_switch]]]\" " : "")
25
+ daemonize true
26
+ end
27
+ end
@@ -0,0 +1,80 @@
1
+ require 'tor'
2
+ require 'rest-client'
3
+
4
+ module TorManager
5
+ class IpAddressControl
6
+ attr_accessor :ip
7
+
8
+ def initialize params={}
9
+ @tor_process = params.fetch(:tor_process, nil)
10
+ @tor_proxy = params.fetch(:tor_proxy, nil)
11
+ @ip = nil
12
+ @endpoint_change_attempts = 5
13
+ end
14
+
15
+ def get_ip
16
+ ensure_tor_is_available
17
+ @ip = tor_endpoint_ip
18
+ end
19
+
20
+ def get_new_ip
21
+ ensure_tor_is_available
22
+ get_new_tor_endpoint_ip
23
+ end
24
+
25
+ private
26
+
27
+ def ensure_tor_is_available
28
+ raise "Cannot proceed, Tor is not running on port " +
29
+ "#{@tor_process.settings[:tor_port]}" unless
30
+ TorProcess.tor_running_on? port: @tor_process.settings[:tor_port],
31
+ parent_pid: @tor_process.settings[:parent_pid]
32
+ end
33
+
34
+ def tor_endpoint_ip
35
+ try_getting_endpoint_ip_restart_tor_and_retry_on_fail attempts: 2
36
+ rescue Exception => ex
37
+ puts "Error getting ip: #{ex.to_s}"
38
+ return nil
39
+ end
40
+
41
+ def try_getting_endpoint_ip_restart_tor_and_retry_on_fail params={}
42
+ ip = nil
43
+ (params[:attempts] || 2).times do |attempt|
44
+ begin
45
+ @tor_proxy.proxy do
46
+ ip = RestClient::Request
47
+ .execute(method: :get,
48
+ url: 'http://bot.whatismyipaddress.com')
49
+ .to_str
50
+ end
51
+ break if ip
52
+ rescue Exception => ex
53
+ @tor_process.stop
54
+ @tor_process.start
55
+ end
56
+ end
57
+ ip
58
+ end
59
+
60
+ def get_new_tor_endpoint_ip
61
+ @endpoint_change_attempts.times do |i|
62
+ tor_switch_endpoint
63
+ new_ip = tor_endpoint_ip
64
+ if new_ip.to_s.length > 0 && new_ip != @ip
65
+ @ip = new_ip
66
+ break
67
+ end
68
+ end
69
+ @ip
70
+ end
71
+
72
+ def tor_switch_endpoint
73
+ Tor::Controller.connect(:port => @tor_process.settings[:control_port]) do |tor|
74
+ tor.authenticate("")
75
+ tor.signal("newnym")
76
+ sleep 10
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,76 @@
1
+ require 'socket'
2
+
3
+ module TorManager
4
+ class ProcessHelper
5
+ class << self
6
+ def query_process query
7
+ return [] unless query
8
+ query_process_bash_cmd(query).split("\n").map{ |query_output_line|
9
+ get_pid_from_query_process_output_line(query_output_line)
10
+ }.compact
11
+ end
12
+
13
+ def kill_process pids
14
+ to_array(pids).each do |pid|
15
+ try_to_kill pid: pid, attempts: 5
16
+ end
17
+ end
18
+
19
+ def process_pid_running? pid
20
+ begin
21
+ return false if pid.to_s == ''.freeze
22
+ ipid = pid.to_i
23
+ return false if ipid <= 0
24
+ Process.kill(0, ipid)
25
+ return true
26
+ rescue
27
+ return false
28
+ end
29
+ end
30
+
31
+ def port_is_open? port
32
+ begin
33
+ server = TCPServer.new('127.0.0.1', port)
34
+ server.close
35
+ return true
36
+ rescue Errno::EADDRINUSE;
37
+ return false
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def query_process_bash_cmd query
44
+ `ps -ef | #{query_grep_pipe_chain(query)} | grep -v grep`
45
+ end
46
+
47
+ def query_grep_pipe_chain query
48
+ to_array(query)
49
+ .map{|q| "grep '#{q}'"}
50
+ .join(' | ')
51
+ end
52
+
53
+ def get_pid_from_query_process_output_line query_output_line
54
+ output_parts = query_output_line.gsub(/\s\s+/, ' ').strip.split
55
+ output_parts.size >= 3 && output_parts[1].to_i > 0 ?
56
+ output_parts[1].to_i : nil
57
+ end
58
+
59
+ def to_array v
60
+ (v.kind_of?(Array) ? v : [v])
61
+ end
62
+
63
+ def try_to_kill params={}
64
+ pid = params.fetch(:pid, nil)
65
+ return unless pid && process_pid_running?(pid)
66
+ params.fetch(:attempts, 5).times do |k|
67
+ k < 3 ? Process.kill('TERM', pid) :
68
+ Process.kill('KILL', pid)
69
+ sleep 0.5
70
+ break unless process_pid_running? pid
71
+ raise "Couldnt kill pid: #{pid}" if k >= 4
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,30 @@
1
+ require 'socksify'
2
+
3
+ module TorManager
4
+ class Proxy
5
+ #Socksify::debug = true
6
+
7
+ def initialize params={}
8
+ @tor_process = params.fetch(:tor_process, nil)
9
+ end
10
+
11
+ def proxy
12
+ enable_socks_server
13
+ yield.tap { disable_socks_server }
14
+ ensure
15
+ disable_socks_server
16
+ end
17
+
18
+ private
19
+
20
+ def enable_socks_server
21
+ TCPSocket::socks_server = "127.0.0.1"
22
+ TCPSocket::socks_port = @tor_process.settings[:tor_port]
23
+ end
24
+
25
+ def disable_socks_server
26
+ TCPSocket::socks_server = nil
27
+ TCPSocket::socks_port = nil
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,193 @@
1
+ require 'eye'
2
+ require 'eyemanager'
3
+ require 'fileutils'
4
+ require 'securerandom'
5
+
6
+ module TorManager
7
+ class TorProcess
8
+ attr_accessor :settings
9
+
10
+ def initialize params={}
11
+ @settings = {}
12
+ @settings[:tor_port] = params.fetch(:tor_port, 9050)
13
+ @settings[:control_port] = params.fetch(:control_port, 50500)
14
+ @settings[:pid_dir] = params.fetch(:pid_dir, '/tmp'.freeze)
15
+ @settings[:log_dir] = params.fetch(:log_dir, '/tmp'.freeze)
16
+ @settings[:tor_data_dir] = params.fetch(:tor_data_dir, nil)
17
+ @settings[:tor_new_circuit_period] = params.fetch(:tor_new_circuit_period, 60)
18
+ @settings[:max_tor_memory_usage_mb] = params.fetch(:max_tor_memory_usage, 200)
19
+ @settings[:max_tor_cpu_percentage] = params.fetch(:max_tor_cpu_percentage, 10)
20
+ @settings[:eye_tor_config_template] =
21
+ params.fetch(:eye_tor_config_template,
22
+ File.join(File.dirname(__dir__),'tormanager/eye/tor.template.eye.rb'))
23
+ @settings[:parent_pid] = Process.pid
24
+ @settings[:control_password] = params.fetch(:control_password, random_password)
25
+ @settings[:hashed_control_password] =
26
+ tor_hash_password_from(@settings[:control_password])
27
+ @settings[:tor_log_switch] = params.fetch(:tor_log_switch, nil)
28
+ @settings[:eye_logging] = params.fetch(:eye_logging, nil)
29
+ @settings[:tor_logging] = params.fetch(:tor_logging, nil)
30
+ @settings[:dont_remove_tor_config] = params.fetch(:dont_remove_tor_config, nil)
31
+ end
32
+
33
+ def start
34
+ prepare_tor_start_and_monitor if tor_ports_are_open?
35
+ end
36
+
37
+ def stop
38
+ EyeManager.stop application: eye_app_name, process: 'tor'
39
+ remove_eye_tor_config unless @settings[:dont_remove_tor_config]
40
+ ensure_tor_is_down
41
+ end
42
+
43
+ class << self
44
+ def stop_obsolete_processes
45
+ (EyeManager.list_apps || []).each do |app|
46
+ EyeManager.stop(application: app, process: 'tor') unless
47
+ ProcessHelper.process_pid_running? pid_of_tor_eye_process(app)
48
+ end
49
+ end
50
+
51
+ def tor_running_on? params={}
52
+ is_running = false
53
+ (EyeManager.list_apps || []).each do |app|
54
+ if port_and_or_pid_matches_eye_tor_name?(app, params) &&
55
+ EyeManager.status(application: app,
56
+ process: 'tor') == 'up'
57
+ is_running = true
58
+ break
59
+ end
60
+ end
61
+ is_running
62
+ end
63
+
64
+ private
65
+
66
+ def port_and_or_pid_matches_eye_tor_name? app, params={}
67
+ (params[:port] || params[:parent_pid]) &&
68
+ (!params[:port] || port_of_tor_eye_process(app).to_i == params[:port].to_i) &&
69
+ (!params[:parent_pid] || pid_of_tor_eye_process(app).to_i == params[:parent_pid].to_i)
70
+ end
71
+
72
+ def pid_of_tor_eye_process app
73
+ app.to_s.split('-').last
74
+ end
75
+
76
+ def port_of_tor_eye_process app
77
+ app_name_split = app.to_s.split('-')
78
+ app_name_split.length >= 3 ?
79
+ app_name_split[2].to_i : nil
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ def tor_ports_are_open?
86
+ tor_port_is_open? &&
87
+ control_port_is_open?
88
+ end
89
+
90
+ def tor_port_is_open?
91
+ raise "Cannot spawn Tor process as port " +
92
+ "#{@settings[:tor_port]} is in use" unless
93
+ ProcessHelper.port_is_open?(@settings[:tor_port])
94
+ true
95
+ end
96
+
97
+ def control_port_is_open?
98
+ raise "Cannot spawn Tor process as control port " +
99
+ "#{@settings[:control_port]} is in use" unless
100
+ ProcessHelper.port_is_open?(@settings[:control_port])
101
+ true
102
+ end
103
+
104
+ def prepare_tor_start_and_monitor
105
+ build_eye_config_from_template
106
+ make_dirs
107
+ start_tor_and_monitor
108
+ end
109
+
110
+ def build_eye_config_from_template
111
+ File.open(eye_config_filename, "w") do |file|
112
+ file.puts read_eye_tor_config_template_and_substitute_keywords
113
+ end
114
+ end
115
+
116
+ def eye_config_filename
117
+ @eye_config_filename || File.join(@settings[:log_dir],
118
+ "tormanager.tor.#{@settings[:tor_port]}.#{Process.pid}.eye.rb")
119
+ end
120
+
121
+ def eye_app_name
122
+ @eye_app_name || "tormanager-tor-#{@settings[:tor_port]}-#{Process.pid}"
123
+ end
124
+
125
+ def read_eye_tor_config_template_and_substitute_keywords
126
+ text = File.read(@settings[:eye_tor_config_template])
127
+ eye_tor_config_template_substitution_keywords.each do |keyword|
128
+ text = text.gsub(/\[\[\[#{keyword}\]\]\]/, @settings[keyword.to_sym].to_s)
129
+ end
130
+ text
131
+ end
132
+
133
+ def eye_tor_config_template_substitution_keywords
134
+ remove_settings_that_are_not_eye_tor_config_template_keywords(
135
+ @settings.keys.map(&:to_s))
136
+ end
137
+
138
+ def remove_settings_that_are_not_eye_tor_config_template_keywords keywords
139
+ keywords - ['eye_tor_config_template', 'control_password', 'dont_remove_tor_config']
140
+ end
141
+
142
+ def make_dirs
143
+ [@settings[:log_dir], @settings[:pid_dir],
144
+ @settings[:tor_data_dir]].each do |path|
145
+ FileUtils.mkpath(path) if path && !File.exists?(path)
146
+ end
147
+ end
148
+
149
+ def start_tor_and_monitor
150
+ EyeManager.start config: eye_config_filename,
151
+ application: eye_app_name
152
+ ensure_tor_is_up
153
+ end
154
+
155
+ def ensure_tor_is_up
156
+ 10.times do |i|
157
+ break if
158
+ EyeManager.status(
159
+ application: eye_app_name,
160
+ process: 'tor') == 'up'
161
+ sleep 2
162
+ raise "Tor didnt start up after 20 seconds! See log: " +
163
+ "#{File.join(@settings[:log_dir],
164
+ eye_app_name + ".log")}" if i >= 9
165
+ end
166
+ end
167
+
168
+ def ensure_tor_is_down
169
+ 10.times do |i|
170
+ tor_status = EyeManager.status(
171
+ application: eye_app_name,
172
+ process: 'tor')
173
+ break if ['unknown','unmonitored'].include?(tor_status)
174
+ sleep 2
175
+ raise "Tor didnt stop after 20 seconds! Last status: #{tor_status} See log: " +
176
+ "#{File.join(@settings[:log_dir],
177
+ eye_app_name + ".log")}" if i >= 9
178
+ end
179
+ end
180
+
181
+ def remove_eye_tor_config
182
+ File.delete(eye_config_filename) if File.exists?(eye_config_filename)
183
+ end
184
+
185
+ def random_password
186
+ SecureRandom.random_number(36**12).to_s(36).rjust(12, "0")
187
+ end
188
+
189
+ def tor_hash_password_from password
190
+ `tor --quiet --hash-password '#{password}'`.strip
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,3 @@
1
+ module TorManager
2
+ VERSION = "0.1.0"
3
+ end
data/lib/tormanager.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "tormanager/version"
2
+ require "tormanager/process_helper"
3
+ require "tormanager/tor_process"
4
+ require "tormanager/proxy"
5
+ require "tormanager/ip_address_control"
6
+
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tormanager/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "tormanager"
8
+ spec.version = TorManager::VERSION
9
+ spec.authors = ["joshweir"]
10
+ spec.email = ["joshua.weir@gmail.com"]
11
+
12
+ spec.summary = %q{Start, stop, monitor, control and proxy through a Tor process using Ruby.}
13
+ #spec.description = %q{TODO: Write a longer description or delete this line.}
14
+ spec.homepage = "https://github.com/joshweir/tormanager"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.14"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rspec", "~> 3.0"
27
+ spec.add_development_dependency "rspec-rails", "~> 3.5"
28
+ spec.add_dependency "rest-client"
29
+ spec.add_dependency "eye"
30
+ spec.add_dependency "eyemanager"
31
+ spec.add_dependency "gem-release"
32
+ spec.add_dependency "socksify"
33
+ end
metadata ADDED
@@ -0,0 +1,191 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tormanager
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - joshweir
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-08-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.5'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.5'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rest-client
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: eye
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: eyemanager
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: gem-release
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: socksify
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ description:
140
+ email:
141
+ - joshua.weir@gmail.com
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - ".gitignore"
147
+ - ".rspec"
148
+ - ".travis.yml"
149
+ - Gemfile
150
+ - Guardfile
151
+ - LICENSE.txt
152
+ - README.md
153
+ - Rakefile
154
+ - bin/console
155
+ - bin/setup
156
+ - lib/tormanager.rb
157
+ - lib/tormanager/eye/eye.tor.test.rb
158
+ - lib/tormanager/eye/sugar.rb
159
+ - lib/tormanager/eye/tor.god.rb
160
+ - lib/tormanager/eye/tor.template.eye.rb
161
+ - lib/tormanager/ip_address_control.rb
162
+ - lib/tormanager/process_helper.rb
163
+ - lib/tormanager/proxy.rb
164
+ - lib/tormanager/tor_process.rb
165
+ - lib/tormanager/version.rb
166
+ - tormanager.gemspec
167
+ homepage: https://github.com/joshweir/tormanager
168
+ licenses:
169
+ - MIT
170
+ metadata: {}
171
+ post_install_message:
172
+ rdoc_options: []
173
+ require_paths:
174
+ - lib
175
+ required_ruby_version: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
180
+ required_rubygems_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: '0'
185
+ requirements: []
186
+ rubyforge_project:
187
+ rubygems_version: 2.5.1
188
+ signing_key:
189
+ specification_version: 4
190
+ summary: Start, stop, monitor, control and proxy through a Tor process using Ruby.
191
+ test_files: []