diff --git a/security/tor/Makefile b/security/tor/Makefile index 6d938aa2e..59ce57587 100644 --- a/security/tor/Makefile +++ b/security/tor/Makefile @@ -1,7 +1,7 @@ PLUGIN_NAME= tor -PLUGIN_VERSION= 1.1 +PLUGIN_VERSION= 1.2 PLUGIN_COMMENT= The Onion Router -PLUGIN_DEPENDS= tor +PLUGIN_DEPENDS= tor ruby PLUGIN_MAINTAINER= franz.fabian.94@gmail.com .include "../../Mk/plugins.mk" diff --git a/security/tor/src/opnsense/mvc/app/controllers/OPNsense/Tor/Api/ServiceController.php b/security/tor/src/opnsense/mvc/app/controllers/OPNsense/Tor/Api/ServiceController.php index 0846d2059..2de3eb487 100644 --- a/security/tor/src/opnsense/mvc/app/controllers/OPNsense/Tor/Api/ServiceController.php +++ b/security/tor/src/opnsense/mvc/app/controllers/OPNsense/Tor/Api/ServiceController.php @@ -157,4 +157,24 @@ class ServiceController extends ApiControllerBase return array('status' => 'failed'); } } + /** + * query tor circuits + * @return array + */ + public function circuitsAction() + { + $backend = new Backend(); + $response = json_decode($backend->configdRun('tor circuit')); + return array('response' => $response); + } + /** + * query tor streams + * @return array + */ + public function streamsAction() + { + $backend = new Backend(); + $response = json_decode($backend->configdRun('tor streams')); + return array('response' => $response); + } } diff --git a/security/tor/src/opnsense/mvc/app/controllers/OPNsense/Tor/IndexController.php b/security/tor/src/opnsense/mvc/app/controllers/OPNsense/Tor/IndexController.php index c5a04e4cf..881e687b4 100644 --- a/security/tor/src/opnsense/mvc/app/controllers/OPNsense/Tor/IndexController.php +++ b/security/tor/src/opnsense/mvc/app/controllers/OPNsense/Tor/IndexController.php @@ -54,4 +54,19 @@ class IndexController extends \OPNsense\Base\IndexController $this->view->title = gettext("The Onion Router - Information"); $this->view->pick('OPNsense/Tor/info'); } + public function diagnosticsAction() + { + $this->view->title = gettext("The Onion Router - Diagnostics"); + if ($this->is_tor_running()) { + $this->view->pick('OPNsense/Tor/diagnostics'); + } + else { + $this->view->pick('OPNsense/Tor/error'); + } + } + private function is_tor_running() + { + $status = (new Api\ServiceController())->statusAction(); + return $status['status'] == 'running'; + } } diff --git a/security/tor/src/opnsense/mvc/app/models/OPNsense/Tor/Menu/Menu.xml b/security/tor/src/opnsense/mvc/app/models/OPNsense/Tor/Menu/Menu.xml index a39ace8ad..864e2ef34 100644 --- a/security/tor/src/opnsense/mvc/app/models/OPNsense/Tor/Menu/Menu.xml +++ b/security/tor/src/opnsense/mvc/app/models/OPNsense/Tor/Menu/Menu.xml @@ -1,8 +1,9 @@ - - - - + + + + + diff --git a/security/tor/src/opnsense/mvc/app/views/OPNsense/Tor/diagnostics.volt b/security/tor/src/opnsense/mvc/app/views/OPNsense/Tor/diagnostics.volt new file mode 100644 index 000000000..71fd0a0ff --- /dev/null +++ b/security/tor/src/opnsense/mvc/app/views/OPNsense/Tor/diagnostics.volt @@ -0,0 +1,122 @@ +{# + + Copyright (C) 2017 Fabian Franz + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + +#} + + + + + +
+
+ + + + + + + + + + + +
{{ lang._('Stream ID') }}{{ lang._('Stream Status') }}{{ lang._('Circuit ID') }}{{ lang._('Destination Host') }}{{ lang._('Destination Port') }}
+
+
+ + + + + + + + + + +
{{ lang._('Circuit ID') }}{{ lang._('Status') }}{{ lang._('Hosts') }}{{ lang._('Flags') }}
+
+
diff --git a/security/tor/src/opnsense/mvc/app/views/OPNsense/Tor/error.volt b/security/tor/src/opnsense/mvc/app/views/OPNsense/Tor/error.volt new file mode 100644 index 000000000..71500976a --- /dev/null +++ b/security/tor/src/opnsense/mvc/app/views/OPNsense/Tor/error.volt @@ -0,0 +1,32 @@ +{# + + Copyright (C) 2017 Fabian Franz + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + +#} + + diff --git a/security/tor/src/opnsense/mvc/app/views/OPNsense/Tor/info.volt b/security/tor/src/opnsense/mvc/app/views/OPNsense/Tor/info.volt index 675458701..bb41fd704 100644 --- a/security/tor/src/opnsense/mvc/app/views/OPNsense/Tor/info.volt +++ b/security/tor/src/opnsense/mvc/app/views/OPNsense/Tor/info.volt @@ -44,7 +44,7 @@ $( document ).ready(function() { var tmp = ''; for (var name in data) { if (data.hasOwnProperty(name)) { - tmp += '' + name + '' + data[name] + ''; + tmp += '' + name + '' + data[name].replace("\n",'
') + ''; } } @@ -61,7 +61,7 @@ $( document ).ready(function() {
- +
diff --git a/security/tor/src/opnsense/scripts/tor/tor_diag b/security/tor/src/opnsense/scripts/tor/tor_diag new file mode 100755 index 000000000..edd9f3bbf --- /dev/null +++ b/security/tor/src/opnsense/scripts/tor/tor_diag @@ -0,0 +1,176 @@ +#!/usr/local/bin/ruby +=begin +Copyright 2017 Fabian Franz +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +=end + + +require 'enumerator' +require 'json' +require 'optparse' +require 'pp' +require 'socket' +require 'rexml/document' + +# global for showing debug output if needed +$TOR_DEBUG = false + + +config = REXML::Document.new(File.new("/conf/config.xml")) +$TOR_PASSWORD = config.elements['opnsense/OPNsense/tor/general/control_port_password'].text + +class TorCTL + + def initialize + @tor = TCPSocket.new("127.0.0.1", 9051) + send_query("AUTHENTICATE \"#{$TOR_PASSWORD}\"") + end + + + # new ip: signal NEWNYM + + def get_version + { version: send_query("GETINFO version") } + end + + def close + @tor.puts("QUIT") + @tor.close + end + + def send_query(str) + @tor.puts(str) + ret = '' + while (data = @tor.gets&.strip) && (!data.start_with? "250 ") + begin + res = data.scan(/^(\d{3})(.*)$/).first + if res[1][0] == '-' + ret << res[1][1..-1] + "\n" + else + ret << res[1] + "\n" + end + rescue + ret << data + "\n" + end + end + ret + end + + def get_config(str) + send_query("getconf #{str}") + end + + def get_info(str) + d = send_query("getinfo #{str}") + lines = d.lines + if lines.first.end_with? "=\n" + lines.shift # property= + lines.pop # . + else + lines[0] = lines[0][((lines[0].index "=") + 1)...(lines[0].length)] + end + lines.join + end + + def get_circuit + lines = get_info('circuit-status').lines + pp lines if $TOR_DEBUG + lines.each(&:strip!) + ret = {} + lines.each do |line| + data = line.split(' ') + circuit_id = data.shift.to_i + status = data.shift + hosts = data.shift.split(',').map do |host_nickname| + h,n = host_nickname.split('~') + { host: h, nickname: n } + end + flags = data.map do |flag_entry| + flag_key, flag_value = flag_entry.split('=') + [flag_key, flag_value.split(',')] + end.to_h + + ret[circuit_id] = { status: status, + hosts: hosts, + flags: flags } + end + ret + end + + def stream_status + stream_status_data = get_info("stream-status") + result = [] + stream_status_data.split(' ').each_slice(4) do |data_set| + d_ip, d_port = data_set[3].scan(/(.*):(\d+)/).first + result << { stream_id: data_set[0], + stream_status: data_set[1], + circuit_id: data_set[2], + destination_host: d_ip, + destination_port: d_port + } + end + result + end +end + + +options = {} + +OptionParser.new do |opts| + opts.banner = "Usage: #{__FILE__} ARGS" + opts.on("-s", "--stream_status", "Print stream status") do |od| + options[:action] = :stream_status + end + opts.on("-c", "--circuit", 'Print the Circuit') do |od| + options[:action] = :get_circuit + end + opts.on("-v", "--version", 'Print the Tor version') do |od| + options[:action] = :get_version + end + opts.on("-x", "--debug", "Prints debug output") do |od| + $TOR_DEBUG = true + end + opts.on("-h", "--help", "Prints this help") do + puts opts + exit + end +end.parse! + +tor = TorCTL.new + +no_param_actions = [:get_circuit, :stream_status, :get_version] +if no_param_actions.include? options[:action] + begin + data = tor.send(options[:action]) + if data + puts data.to_json + else + puts error: "no data" + end + rescue + puts error: "execution error", error_str: $! + end +end + +#puts tor.get_config("controlport") +tor.close + + diff --git a/security/tor/src/opnsense/service/conf/actions.d/actions_tor.conf b/security/tor/src/opnsense/service/conf/actions.d/actions_tor.conf index 1eab63721..ad3672a4f 100644 --- a/security/tor/src/opnsense/service/conf/actions.d/actions_tor.conf +++ b/security/tor/src/opnsense/service/conf/actions.d/actions_tor.conf @@ -33,3 +33,15 @@ command:/usr/local/opnsense/scripts/tor/get_hostnames parameters: type:script_output message:Query hostnames of Onion services + +[circuit] +command:/usr/local/opnsense/scripts/tor/tor_diag -c +parameters: +type:script_output +message:Query Tor circuit + +[streams] +command:/usr/local/opnsense/scripts/tor/tor_diag -s +parameters: +type:script_output +message:Query Tor stream information
{{ lang._('Onion Service Name') }}