spior 0.1.6 → 0.3.5

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.
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tempfile'
4
+ require 'fileutils'
5
+ require 'nomansland'
6
+
7
+ module Spior
8
+ module Iptables
9
+ # Iptables::Rules, used to save or restore iptables rules
10
+ class Rules
11
+ def initialize
12
+ @tmp_iptables_rules = Tempfile.new('iptables_rules')
13
+ @tmp_spior_rules = Tempfile.new('spior_rules')
14
+ @save_path = search_iptables_config
15
+ end
16
+
17
+ def save
18
+ save_rules(@tmp_iptables_rules)
19
+ insert_comment(@tmp_spior_rules, @tmp_iptables_rules)
20
+ create_file(@tmp_spior_rules, @save_path)
21
+ Msg.p "Iptables rules saved at #{@save_path}"
22
+ end
23
+
24
+ def restore
25
+ return if restoring_older_rules(@save_path)
26
+
27
+ Msg.p 'Adding clearnet navigation...'
28
+ Iptables::Default.new.run!
29
+ end
30
+
31
+ protected
32
+
33
+ def save_rules(tmp_file)
34
+ Msg.p 'Saving Iptables rules...'
35
+ Helpers::Exec.new('iptables-save').run("> #{tmp_file.path}")
36
+ end
37
+
38
+ def insert_comment(spior_file, iptable_file)
39
+ outfile = File.open(spior_file.path, 'w')
40
+ outfile.puts '# Rules saved by Spior.'
41
+ outfile.puts(File.read(iptable_file.path))
42
+ outfile.close
43
+ end
44
+
45
+ def search_for_comment(filename)
46
+ File.open(filename) do |f|
47
+ f.each do |line|
48
+ return true if line.match(/saved by Spior/)
49
+ end
50
+ end
51
+ false
52
+ end
53
+
54
+ def move(src, dest)
55
+ if Process::Sys.getuid == '0'
56
+ FileUtils.mv(src, dest)
57
+ else
58
+ Helpers::Exec.new('mv').run("#{src} #{dest}")
59
+ end
60
+ end
61
+
62
+ def create_file(tmpfile, dest)
63
+ if File.exist? dest
64
+ if search_for_comment(dest)
65
+ Msg.p "Older Spior rules found #{dest}, erasing..."
66
+ else
67
+ Msg.p "File exist #{dest}, create backup #{dest}-backup..."
68
+ move(dest, "#{dest}-backup")
69
+ end
70
+ end
71
+ move(tmpfile.path, dest)
72
+ end
73
+
74
+ def restoring_older_rules(filename)
75
+ files = %W[#{filename}-backup #{filename}]
76
+ files.each do |f|
77
+ next unless File.exist?(f) || search_for_comment(f)
78
+
79
+ Iptables::Root.new.stop!
80
+ Msg.p "Found older rules #{f}, restoring..."
81
+ Helpers::Exec.new('iptables-restore').run(f)
82
+ return true
83
+ end
84
+ false
85
+ end
86
+
87
+ private
88
+
89
+ def search_iptables_config
90
+ case Nomansland.distro?
91
+ when :archlinux || :void
92
+ '/etc/iptables/iptables.rules'
93
+ when :debian
94
+ '/etc/iptables.up.rules'
95
+ when :gentoo
96
+ '/var/lib/iptables/rules-save'
97
+ else
98
+ Msg.report 'I don`t know where you distro save the rules for iptables yet'
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -1,58 +1,59 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Spior
2
4
  module Iptables
5
+ # Make Local Redirection Through Tor.
3
6
  class Tor < Iptables::Root
4
7
  def initialize
5
8
  super
6
- @tor = Spior::Tor::Info.new
7
- @non_tor = ["#{@lo_addr}/8", "192.168.0.0/16", "172.16.0.0/12", "10.0.0.0/8"]
8
- @tables = ["nat", "filter"]
9
+ @non_tor = %W[#{@lo_addr}/8 192.168.0.0/16 172.16.0.0/12 10.0.0.0/8]
10
+ @tables = %w[nat filter]
9
11
  end
10
12
 
11
13
  private
12
14
 
13
15
  def redirect
14
- @tables.each { |table|
15
- target = "ACCEPT"
16
- target = "RETURN" if table == "nat"
16
+ Msg.p 'Redirecting local traffic though Tor...'
17
+ @tables.map do |table|
18
+ target = 'ACCEPT'
19
+ target = 'RETURN' if table == 'nat'
17
20
 
18
21
  ipt "-t #{table} -F OUTPUT"
19
22
  ipt "-t #{table} -A OUTPUT -m state --state ESTABLISHED -j #{target}"
20
- ipt "-t #{table} -A OUTPUT -m owner --uid #{@tor.uid} -j #{target}"
23
+ ipt "-t #{table} -A OUTPUT -m owner --uid #{CONFIG.uid} -j #{target}"
21
24
 
22
- match_dns_port = @tor.dns
23
- if table == "nat"
24
- target = "REDIRECT --to-ports #{@tor.dns}"
25
- match_dns_port = "53"
25
+ match_dns_port = CONFIG.dns_port
26
+ if table == 'nat'
27
+ target = "REDIRECT --to-ports #{CONFIG.dns_port}"
28
+ match_dns_port = '53'
26
29
  end
27
30
 
28
31
  ipt "-t #{table} -A OUTPUT -p udp --dport #{match_dns_port} -j #{target}"
29
32
  ipt "-t #{table} -A OUTPUT -p tcp --dport #{match_dns_port} -j #{target}"
30
33
 
31
- target = "REDIRECT --to-ports #{@tor.trans_port}" if table == "nat"
32
- ipt "-t #{table} -A OUTPUT -d #{@tor.virt_addr} -p tcp -j #{target}"
34
+ target = "REDIRECT --to-ports #{CONFIG.trans_port}" if table == 'nat'
35
+ ipt "-t #{table} -A OUTPUT -d #{CONFIG.virt_addr} -p tcp -j #{target}"
33
36
 
34
- target = "RETURN" if table == "nat"
35
- @non_tor.each { |ip|
36
- ipt "-t #{table} -A OUTPUT -d #{ip} -j #{target}"
37
- }
37
+ target = 'RETURN' if table == 'nat'
38
+ @non_tor.each { |ip| ipt "-t #{table} -A OUTPUT -d #{ip} -j #{target}" }
38
39
 
39
- target = "REDIRECT --to-ports #{@tor.trans_port}" if table == "nat"
40
+ target = "REDIRECT --to-ports #{CONFIG.trans_port}" if table == 'nat'
40
41
  ipt "-t #{table} -A OUTPUT -p tcp -j #{target}"
41
- }
42
+ end
42
43
  end
43
44
 
44
45
  def input
45
46
  # SSH
46
- ipt "-A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT"
47
+ ipt '-A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT'
47
48
  # Allow loopback
48
49
  ipt "-A INPUT -i #{@lo} -j ACCEPT"
49
50
  # Accept related
50
- ipt "-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT"
51
+ ipt '-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT'
51
52
  end
52
53
 
53
54
  def all
54
- ipt "-t filter -A OUTPUT -p udp -j REJECT"
55
- ipt "-t filter -A OUTPUT -p icmp -j REJECT"
55
+ ipt '-t filter -A OUTPUT -p udp -j REJECT'
56
+ ipt '-t filter -A OUTPUT -p icmp -j REJECT'
56
57
  end
57
58
  end
58
59
  end
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Spior
4
+ # Interact with iptables
2
5
  module Iptables
3
6
  end
4
7
  end
@@ -6,3 +9,4 @@ end
6
9
  require_relative 'iptables/root'
7
10
  require_relative 'iptables/tor'
8
11
  require_relative 'iptables/default'
12
+ require_relative 'iptables/rules'
data/lib/spior/ipv6.rb ADDED
@@ -0,0 +1,35 @@
1
+ # lib/ipv6.rb
2
+ # frozen_string_literal: true
3
+
4
+ require 'auth'
5
+
6
+ module Spior
7
+ # Block or Allow ipv6 traffic with sysctl
8
+ class Ipv6
9
+ def initialize
10
+ @changed = false
11
+ end
12
+
13
+ def allow
14
+ apply_option('net.ipv6.conf.all.disable_ipv6', '0')
15
+ apply_option('net.ipv6.conf.default.disable_ipv6', '0')
16
+ Msg.p 'ipv6 allowed' if @changed
17
+ end
18
+
19
+ def block
20
+ apply_option('net.ipv6.conf.all.disable_ipv6', '1')
21
+ apply_option('net.ipv6.conf.default.disable_ipv6', '1')
22
+ Msg.p 'ipv6 blocked' if @changed
23
+ end
24
+
25
+ private
26
+
27
+ def apply_option(flag, value)
28
+ flag_path = flag.gsub('.', '/')
29
+ return unless File.exist?("/proc/sys/#{flag_path}")
30
+
31
+ Auth.new.sysctl(flag, value)
32
+ @changed = true
33
+ end
34
+ end
35
+ end
data/lib/spior/menu.rb CHANGED
@@ -1,43 +1,37 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Spior
4
+ # Build an interactive menu for spior
2
5
  module Menu
3
- extend self
4
-
5
- def run
6
- banner
6
+ def self.run
7
7
  loop do
8
8
  Msg.head
9
- puts %q{Please select an option:
9
+ puts 'Please select an option:
10
10
 
11
- 1. Redirect traffic through tor
12
- 2. Reload tor and change your ip
13
- 3. Clear and restore your files
14
- 4. Check info on your current ip
15
- 5. Quit}
11
+ 1. Redirect traffic through Tor
12
+ 2. Reload Spior and change your IP
13
+ 3. Stop Tor and use a clearnet navigation
14
+ 4. Check info on your current IP
15
+ 5. Install all the dependencies
16
+ 6. Quit'
16
17
 
17
18
  puts
18
- print ">> "
19
+ print '>> '
19
20
  case gets.chomp
20
21
  when '1'
21
- Spior::Iptables::Tor.new.run!
22
+ Service.start
22
23
  when '2'
23
- Spior::Serice.restart
24
+ Service.restart
24
25
  when '3'
25
- Spior::Clear.all
26
+ Service.stop
26
27
  when '4'
27
- Spior::Status.info
28
+ Status.info
28
29
  when '5'
30
+ Dep.looking
31
+ else
29
32
  exit
30
33
  end
31
34
  end
32
35
  end
33
-
34
- private
35
-
36
- def banner
37
- puts "┏━┓┏━┓╻┏━┓┏━┓"
38
- puts "┗━┓┣━┛┃┃ ┃┣┳┛"
39
- puts "┗━┛╹ ╹┗━┛╹┗╸"
40
- # generated with toilet -F crop -f future spior
41
- end
42
36
  end
43
37
  end
data/lib/spior/msg.rb CHANGED
@@ -1,28 +1,50 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rainbow'
2
4
 
5
+ # Used to display various message
3
6
  module Msg
4
- extend self
7
+ module_function
8
+
9
+ def banner
10
+ puts
11
+ puts '┏━┓┏━┓╻┏━┓┏━┓'
12
+ puts '┗━┓┣━┛┃┃ ┃┣┳┛'
13
+ puts '┗━┛╹ ╹┗━┛╹┗╸'
14
+ puts
15
+ # generated with toilet -F crop -f future spior
16
+ end
5
17
 
6
18
  def head
7
- puts Rainbow("------------------------------------------------").cyan
19
+ puts Rainbow('------------------------------------------------').cyan
8
20
  end
9
21
 
10
22
  def p(text)
11
- puts Rainbow("[").cyan + Rainbow("+").white + Rainbow("]").cyan + " " + text
23
+ opn = Rainbow('[').cyan
24
+ msg = Rainbow('+').white
25
+ cls = Rainbow(']').cyan
26
+ puts "#{opn}#{msg}#{cls} #{text}"
12
27
  end
13
28
 
14
29
  def err(text)
15
- puts Rainbow("[").red + Rainbow("-").white + Rainbow("]").red + " " + text
30
+ opn = Rainbow('[').red
31
+ msg = Rainbow('-').white
32
+ cls = Rainbow(']').red
33
+ puts "#{opn}#{msg}#{cls} #{text}"
16
34
  end
17
35
 
18
36
  def info(text)
19
- puts Rainbow("-").blue + Rainbow("-").white + Rainbow("-").blue + " " + text + " " + Rainbow("-").blue + Rainbow("-").white + Rainbow("-").blue
37
+ one = Rainbow('_').blue
38
+ two = Rainbow('-').white
39
+ thr = Rainbow('_').blue
40
+ puts "#{one}#{two}#{thr} #{text} #{one}#{two}#{thr}"
20
41
  end
21
42
 
22
43
  def report(text)
23
- puts ""
44
+ puts
24
45
  info text
25
- puts "Please, report this issue at https://github.com/szorfein/spior/issues"
26
- puts ""
46
+ puts 'Please, report this issue at https://github.com/szorfein/spior/issues'
47
+ puts
48
+ exit 1
27
49
  end
28
50
  end
data/lib/spior/options.rb CHANGED
@@ -1,13 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'optparse'
2
4
 
3
5
  module Spior
6
+ # Options for the CLI
4
7
  class Options
5
- attr_reader :install , :tor , :persist
6
-
7
8
  def initialize(argv)
8
- @install = false
9
- @tor = false
10
- @persist = false
11
9
  parse(argv)
12
10
  end
13
11
 
@@ -15,46 +13,46 @@ module Spior
15
13
 
16
14
  def parse(argv)
17
15
  OptionParser.new do |opts|
18
- opts.on("-i", "--install", "Install the dependencies") do
19
- @install = true
16
+ opts.on('-i', '--install', 'Install the dependencies.') do
17
+ Dep.looking
20
18
  end
21
19
 
22
- opts.on("-t", "--tor", "Redirect traffic through TOR") do
23
- @tor = true
20
+ opts.on('-t', '--tor', 'Redirect traffic through TOR.') do
21
+ Service.start
24
22
  end
25
23
 
26
- opts.on("-r", "--reload", "Reload TOR to change your ip") do
27
- Spior::Service.restart
24
+ opts.on('-r', '--reload', 'Reload TOR to change your IP.') do
25
+ Service.restart
28
26
  exit
29
27
  end
30
28
 
31
- opts.on("-c", "--clearnet", "Reset iptables and return to clearnet navigation") do
32
- Spior::Clear.all
29
+ opts.on('-c', '--clearnet', 'Reset iptables and return to clearnet navigation.') do
30
+ Service.stop
33
31
  end
34
32
 
35
- opts.on("-s", "--status", "Look infos about your current ip") do
36
- Spior::Status.info
33
+ opts.on('-s', '--status', 'Look infos about your current IP.') do
34
+ Status.info
37
35
  exit
38
36
  end
39
37
 
40
- opts.on("-p", "--persist", "Active Spior at every boot.") do
41
- @persist = true
38
+ opts.on('-p', '--persist', 'Active Spior at every boot.') do
39
+ Service::Enable.new
42
40
  end
43
41
 
44
- opts.on("-m", "--menu", "Display an interactive menu") do
45
- Spior::Menu.run
42
+ opts.on('-m', '--menu', 'Display an interactive menu.') do
43
+ Menu.run
46
44
  end
47
45
 
48
- opts.on("-h", "--help", "Show this message") do
46
+ opts.on('-h', '--help', 'Show this message.') do
49
47
  puts opts
50
48
  exit
51
49
  end
52
50
 
53
51
  begin
54
- argv = ["-m"] if argv.empty?
52
+ argv = ['-m'] if argv.empty?
55
53
  opts.parse!(argv)
56
54
  rescue OptionParser::ParseError => e
57
- STDERR.puts e.message, "\n", opts
55
+ warn e.message, "\n", opts
58
56
  exit(-1)
59
57
  end
60
58
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nomansland'
4
+
5
+ module Spior
6
+ # Service make Spior persistent using services on system like iptables and tor
7
+ module Service
8
+ # Enable the Tor redirection when you boot your system
9
+ #
10
+ # It should use and enable the services:
11
+ # + tor
12
+ # + iptables
13
+ class Enable
14
+ def initialize
15
+ case Nomansland.distro?
16
+ when :gentoo
17
+ for_gentoo
18
+ when :archlinux
19
+ for_arch
20
+ else
21
+ Msg.report 'Your distro is not yet supported.'
22
+ end
23
+ end
24
+
25
+ protected
26
+
27
+ def for_gentoo
28
+ Iptables::Rules.new.save
29
+ case Nomansland.init?
30
+ when :systemd
31
+ systemd_enable('iptables-restore', 'tor')
32
+ when :openrc
33
+ rc_upd = Helpers::Exec.new('rc-update')
34
+ rc_upd.run('rc-update add iptables boot')
35
+ rc_upd.run('rc-update add tor')
36
+ rc_upd.run('rc-update add tor default')
37
+ else
38
+ Msg.report 'Init no yet supported for start Iptables at boot'
39
+ end
40
+ end
41
+
42
+ def for_arch
43
+ Iptables::Rules.new.save
44
+ Tor::Config.new(Tempfile.new('torrc')).backup
45
+ systemd_enable('iptables', 'tor')
46
+ Msg.p 'Services enabled for Archlinux...'
47
+ end
48
+
49
+ private
50
+
51
+ def systemd_enable(*services)
52
+ systemctl = Helpers::Exec.new('systemctl')
53
+ services.each do |s|
54
+ Msg.p "Search for service #{s}..."
55
+ systemctl.run("enable #{s}") unless system("systemctl is-enabled #{s}")
56
+ end
57
+ end
58
+
59
+ def systemd_start(service)
60
+ systemctl = Helpers::Exec.new('systemctl')
61
+ Msg.p "Search for service #{service}..."
62
+ systemctl.run("start #{service}") unless system("systemctl is-active #{service}")
63
+ end
64
+ end
65
+ end
66
+ end
@@ -1,21 +1,14 @@
1
- require 'tty-which'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Spior
4
+ # Interact with Spior::Tor and Spior::Iptables
4
5
  module Service
5
6
  module_function
6
7
 
7
8
  def restart
8
- if TTY::Which.exist?('systemctl')
9
- Helpers::Exec.new("systemctl").run("restart tor")
10
- Msg.p "ip changed."
11
- elsif TTY::Which.exist? 'sv'
12
- Helpers::Exec.new('sv').run('restart tor')
13
- Msg.p 'ip changed.'
14
- elsif File.exist? '/etc/init.d/tor'
15
- Helpers::Exec.new('/etc/init.d/tor').run('restart')
16
- else
17
- Msg.report "Don't known yet how to restart Tor for your system."
18
- end
9
+ Service.stop(clean: false)
10
+ Service.start
11
+ Msg.p 'ip changed.'
19
12
  end
20
13
  end
21
14
  end
@@ -1,26 +1,16 @@
1
- require 'tty-which'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Spior
4
+ # Interact with Spior::Tor and Spior::Iptables
4
5
  module Service
5
6
  module_function
6
7
 
8
+ # Service.start should start Tor if not alrealy running
9
+ # And start to redirect the local traffic with Iptables
7
10
  def start
8
- if TTY::Which.exist?('systemctl')
9
- state = `systemctl is-active tor`.chomp
10
- unless state == 'active'
11
- Helpers::Exec.new("systemctl").run("start tor")
12
- Msg.p "TOR started."
13
- end
14
- elsif TTY::Which.exist? 'sv'
15
- unless File.exist? '/var/service/tor'
16
- Helpers::Exec.new('ln').run('-s /etc/sv/tor /var/service/tor')
17
- Msg.p "TOR started."
18
- end
19
- elsif File.exist? '/etc/init.d/tor'
20
- Helpers::Exec.new('/etc/init.d/tor').run('start')
21
- else
22
- Msg.report "Don't known yet how to start Tor for your system."
23
- end
11
+ Tor::Start.new
12
+ Iptables::Tor.new.run!
13
+ Ipv6.new.block
24
14
  end
25
15
  end
26
16
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spior
4
+ # Interact with Spior::Tor and Spior::Iptables
5
+ module Service
6
+ module_function
7
+
8
+ def stop(clean: true)
9
+ Tor::Stop.new
10
+ Iptables::Rules.new.restore if clean
11
+ Ipv6.new.allow if clean
12
+ end
13
+ end
14
+ end
data/lib/spior/service.rb CHANGED
@@ -1,7 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Spior
4
+ # Service should start/stop/restart Tor and Iptable.
2
5
  module Service
3
6
  end
4
7
  end
5
8
 
6
9
  require_relative 'service/start'
10
+ require_relative 'service/stop'
7
11
  require_relative 'service/restart'
12
+ require_relative 'service/enable'
data/lib/spior/status.rb CHANGED
@@ -1,38 +1,46 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'open-uri'
2
4
  require 'json'
3
5
 
4
6
  module Spior
7
+ # Status display information on your current IP addresse
8
+ #
9
+ # If you use an IPV6 address, it should fail to display a Tor IP...
5
10
  module Status
11
+ # Check on https://check.torproject.org/api/ip if Tor is enable or not
12
+ # and display the result.
6
13
  def self.enable
7
- begin
8
- status = "Disable"
9
- api_check = "https://check.torproject.org/api/ip"
10
- URI.open(api_check) do |l|
11
- hash = JSON.parse l.read
12
- status = "Enable" if hash["IsTor"] == true
13
- end
14
- status
15
- rescue OpenURI::HTTPError => error
16
- res = error.io
17
- puts "Fail to join server #{res.status}"
14
+ status = 'Disable'
15
+ URI.open('https://check.torproject.org/api/ip') do |l|
16
+ hash = JSON.parse l.read
17
+ status = 'Enable' if hash['IsTor'] == true
18
18
  end
19
+ status
20
+ rescue OpenURI::HTTPError => e
21
+ res = e.io
22
+ puts "Fail to join server #{res.status}"
19
23
  end
20
24
 
25
+ # info check and display information from https://ipleak.net/json
26
+ #
27
+ # Check for:
28
+ # * +ip+
29
+ # * +continent_name+
30
+ # * +time_zone+
31
+ #
32
+ # We can add later info on City/Region or other things.
21
33
  def self.info
22
- begin
23
- api_check = "https://ipleak.net/json"
24
- URI.open(api_check) do |l|
25
- hash = JSON.parse l.read
26
- puts
27
- puts " Current ip ===> #{hash["ip"]}"
28
- puts " Continent ===> #{hash["continent_name"]}"
29
- puts " Timezone ===> #{hash["time_zone"]}"
30
- end
31
- puts " Status ===> #{enable}"
32
- rescue OpenURI::HTTPError => error
33
- res = error.io
34
- puts "Fail to join server #{res.status}"
34
+ URI.open('https://ipleak.net/json') do |l|
35
+ hash = JSON.parse l.read
36
+ puts " Current ip ===> #{hash['ip']}"
37
+ puts " Continent ===> #{hash['continent_name']}"
38
+ puts " Timezone ===> #{hash['time_zone']}"
35
39
  end
40
+ puts " Status ===> #{enable}"
41
+ rescue OpenURI::HTTPError => e
42
+ res = e.io
43
+ puts "Fail to join server #{res.status}"
36
44
  end
37
45
  end
38
46
  end