From a6417c59a7a68b8c8b0bfee97803b301e12467b7 Mon Sep 17 00:00:00 2001 From: James Valleroy Date: Sat, 2 Jan 2016 17:14:33 -0500 Subject: [PATCH] tor: Use augeas to modify configuration. - Use json to get info from action script. - Add SSH port to hidden service. - Enable hidden service during setup. --- actions/tor | 195 +++++++++--------- .../share/augeas/lenses/tests/test_tor.aug | 11 + data/usr/share/augeas/lenses/tor.aug | 36 ++++ plinth/modules/tor/__init__.py | 77 +++---- plinth/modules/tor/templates/tor.html | 10 +- plinth/modules/tor/views.py | 19 +- setup.py | 4 +- 7 files changed, 200 insertions(+), 152 deletions(-) create mode 100644 data/usr/share/augeas/lenses/tests/test_tor.aug create mode 100644 data/usr/share/augeas/lenses/tor.aug diff --git a/actions/tor b/actions/tor index 3fbc68f24..a421e30e5 100755 --- a/actions/tor +++ b/actions/tor @@ -22,7 +22,9 @@ Configuration helper for the Tor service """ import argparse +import augeas import codecs +import json import os import re import socket @@ -33,7 +35,7 @@ from plinth.modules.tor import is_enabled, is_running, get_augeas, \ get_real_apt_uri_path, iter_apt_uris, APT_TOR_PREFIX SERVICE_FILE = '/etc/firewalld/services/tor-{0}.xml' -TOR_CONFIG = '/etc/tor/torrc' +TOR_CONFIG = '/files/etc/tor/torrc' TOR_STATE_FILE = '/var/lib/tor/state' TOR_AUTH_COOKIE = '/var/run/tor/control.authcookie' @@ -44,8 +46,10 @@ def parse_arguments(): subparsers = parser.add_subparsers(dest='subcommand', help='Sub command') subparsers.add_parser('setup', help='Setup Tor configuration') - subparsers.add_parser('get-ports', help='Get list of Tor ports') - subparsers.add_parser('get-hs', help='Get hidden service') + subparsers.add_parser('get-ports', + help='Get Tor ports in JSON format') + subparsers.add_parser('get-hs', + help='Get hidden service information in JSON format') configure = subparsers.add_parser('configure', help='Configure Tor') configure.add_argument('--service', choices=['enable', 'disable'], @@ -61,50 +65,63 @@ def parse_arguments(): def subcommand_setup(_): """Setup Tor configuration after installing it.""" - # XXX: Performing this as a post-install step instead of - # pre-install setup for now. Creating a configuration before hand - # leads dpkg to ask question about configuration overwrite which - # makes aptcc backend of packagekit to wait forever even with - # interactive=False. - lines = """ -# Run as non-exit bridge relay -SocksPort [::]:9050 -SocksPort 0.0.0.0:9050 -ORPort auto -ControlPort 9051 -BridgeRelay 1 -Exitpolicy reject *:* -Exitpolicy reject6 *:* + aug = augeas_load() -# Enable obfsproxy -ServerTransportPlugin obfs3,obfs4 exec /usr/bin/obfs4proxy -ExtORPort auto + aug.set(TOR_CONFIG + '/#comment[last() + 1]', + 'Run as non-exit bridge relay') + aug.set(TOR_CONFIG + '/SocksPort[1]', '[::]:9050') + aug.set(TOR_CONFIG + '/SocksPort[2]', '0.0.0.0:9050') + aug.set(TOR_CONFIG + '/ORPort', 'auto') + aug.set(TOR_CONFIG + '/ControlPort', '9051') + aug.set(TOR_CONFIG + '/BridgeRelay', '1') + aug.set(TOR_CONFIG + '/ExitPolicy[1]', 'reject *:*') + aug.set(TOR_CONFIG + '/ExitPolicy[2]', 'reject6 *:*') -# Enable transparent proxy -VirtualAddrNetworkIPv4 10.192.0.0/10 -AutomapHostsOnResolve 1 -TransPort 127.0.0.1:9040 -TransPort [::1]:9040 -DNSPort 127.0.0.1:9053 -DNSPort [::1]:9053 -""" - with open(TOR_CONFIG, 'w') as conffile: - conffile.writelines(lines) + aug.set(TOR_CONFIG + '/#comment[last() + 1]', 'Enable obfsproxy') + aug.set(TOR_CONFIG + '/ServerTransportPlugin', + 'obfs3,obfs4 exec /usr/bin/obfs4proxy') + aug.set(TOR_CONFIG + '/ExtORPort', 'auto') + + aug.set(TOR_CONFIG + '/#comment[last() + 1]', 'Enable transparent proxy') + aug.set(TOR_CONFIG + '/VirtualAddrNetworkIPv4', '10.192.0.0/10') + aug.set(TOR_CONFIG + '/AutomapHostsOnResolve', '1') + aug.set(TOR_CONFIG + '/TransPort[1]', '127.0.0.1:9040') + aug.set(TOR_CONFIG + '/TransPort[2]', '[::1]:9040') + aug.set(TOR_CONFIG + '/DNSPort[1]', '127.0.0.1:9053') + aug.set(TOR_CONFIG + '/DNSPort[2]', '[::1]:9053') + + aug.set(TOR_CONFIG + '/HiddenServiceDir', + '/var/lib/tor/hidden_service') + aug.set(TOR_CONFIG + '/HiddenServicePort[1]', + '22 127.0.0.1:22') + aug.set(TOR_CONFIG + '/HiddenServicePort[2]', + '80 127.0.0.1:80') + aug.set(TOR_CONFIG + '/HiddenServicePort[3]', + '443 127.0.0.1:443') + + aug.save() action_utils.service_restart('tor') _update_ports() + # wait until hidden service information is available + tries = 0 + while not get_hidden_service()['enabled']: + tries += 1 + if tries >= 12: + return + + time.sleep(10) + def subcommand_get_ports(_): - """Get list of Tor ports.""" - ports = get_ports() - for name, number in ports.items(): - print(name, number) + """Get Tor ports in JSON format.""" + print(json.dumps(get_ports())) def subcommand_get_hs(_): - """Print currently configured Tor hidden service information""" - print(get_hidden_service()) + """Get hidden service information in JSON format.""" + print(json.dumps(get_hidden_service())) def subcommand_configure(arguments): @@ -171,29 +188,32 @@ QUIT def get_hidden_service(): """Return a string with configured Tor hidden service information""" - hs_dir = None + hs_enabled = False + hs_status = 'Ok' + hs_hostname = None hs_ports = [] - try: - with open(TOR_CONFIG, 'r') as conf_file: - for line in conf_file: - if line.startswith('HiddenServiceDir'): - hs_dir = line.split()[1] - elif line.startswith('HiddenServicePort'): - hs_ports.append(line.split()[1]) - except FileNotFoundError: - return 'error' + aug = augeas_load() + hs_dir = aug.get(TOR_CONFIG + '/HiddenServiceDir') + hs_port_paths = aug.match(TOR_CONFIG + '/HiddenServicePort') + + for hs_port_path in hs_port_paths: + port_info = aug.get(hs_port_path).split() + hs_ports.append({'virtport': port_info[0], + 'target': port_info[1]}) if not hs_dir: - return '' + hs_status = 'Not Configured' + else: + try: + with open(os.path.join(hs_dir, 'hostname'), 'r') as conf_file: + hs_hostname = conf_file.read().strip() + hs_enabled = True + except Exception: + hs_status = 'Not available (Run Tor at least once)' - try: - with open(os.path.join(hs_dir, 'hostname'), 'r') as conf_file: - hs_hostname = conf_file.read().strip() - except Exception: - return 'error' - - return hs_hostname + ' ' + ','.join(hs_ports) + return {'enabled': hs_enabled, 'status': hs_status, + 'hostname': hs_hostname, 'ports': hs_ports} def _enable(): @@ -210,20 +230,19 @@ def _disable(): def _enable_hs(restart=True): """Enable Tor hidden service""" - if get_hidden_service(): + if get_hidden_service()['enabled']: return - with open(TOR_CONFIG, 'r') as conffile: - lines = conffile.readlines() - - lines.append('# Hidden Service configured by Plinth\n') - lines.append('HiddenServiceDir /var/lib/tor/hidden_service/\n') - lines.append('HiddenServicePort 80 127.0.0.1:80\n') - lines.append('HiddenServicePort 443 127.0.0.1:443\n') - lines.append('# end of Plinth Hidden Service config\n') - - with open(TOR_CONFIG, 'w') as conffile: - conffile.writelines(lines) + aug = augeas_load() + aug.set(TOR_CONFIG + '/HiddenServiceDir', + '/var/lib/tor/hidden_service') + aug.set(TOR_CONFIG + '/HiddenServicePort[1]', + '22 127.0.0.1:22') + aug.set(TOR_CONFIG + '/HiddenServicePort[2]', + '80 127.0.0.1:80') + aug.set(TOR_CONFIG + '/HiddenServicePort[3]', + '443 127.0.0.1:443') + aug.save() if restart: if is_enabled() and is_running(): @@ -231,7 +250,7 @@ def _enable_hs(restart=True): # wait until hidden service information is available tries = 0 - while get_hidden_service() in ('', 'error'): + while not get_hidden_service()['enabled']: tries += 1 if tries >= 12: return @@ -241,35 +260,13 @@ def _enable_hs(restart=True): def _disable_hs(restart=True): """Disable Tor hidden service""" - if not get_hidden_service(): + if not get_hidden_service()['enabled']: return - with open(TOR_CONFIG, 'r') as conffile: - lines = conffile.readlines() - - filtered_lines = [] - removing = False - for line in lines: - if removing: - if line.startswith('# end of Plinth Hidden Service config'): - # last line of Plinth hidden service block - # stop removing after this line - removing = False - elif not line.startswith('HiddenService'): - # end of Plinth hidden service block - # stop removing lines - removing = False - filtered_lines.append(line) - else: - if line.startswith('# Hidden Service configured by Plinth'): - # start of Plinth hidden service block - # remove following HiddenService lines - removing = True - else: - filtered_lines.append(line) - - with open(TOR_CONFIG, 'w') as conffile: - conffile.writelines(filtered_lines) + aug = augeas_load() + aug.remove(TOR_CONFIG + '/HiddenServiceDir') + aug.remove(TOR_CONFIG + '/HiddenServicePort') + aug.save() if restart: if is_enabled() and is_running(): @@ -347,6 +344,16 @@ def _update_ports(): action_utils.service_restart('firewalld') +def augeas_load(): + """Initialize Augeas.""" + aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD + + augeas.Augeas.NO_MODL_AUTOLOAD) + aug.set('/augeas/load/Tor/lens', 'Tor.lns') + aug.set('/augeas/load/Tor/incl[last() + 1]', '/etc/tor/torrc') + aug.load() + return aug + + def main(): """Parse arguments and perform all duties""" arguments = parse_arguments() diff --git a/data/usr/share/augeas/lenses/tests/test_tor.aug b/data/usr/share/augeas/lenses/tests/test_tor.aug new file mode 100644 index 000000000..73f09cfb1 --- /dev/null +++ b/data/usr/share/augeas/lenses/tests/test_tor.aug @@ -0,0 +1,11 @@ +module Test_Tor = + +test Tor.lns get "\n" = { } +test Tor.lns get "# comment\n" = { "#comment" = "comment" } +test Tor.lns get "SocksPort 9050\n" = { "SocksPort" = "9050" } +test Tor.lns get "SocksPort 0.0.0.0:9050\n" = { "SocksPort" = "0.0.0.0:9050" } +test Tor.lns get "SocksPort [::]:9050\n" = { "SocksPort" = "[::]:9050" } +test Tor.lns get "ExitPolicy reject *:*\n" = { "ExitPolicy" = "reject *:*" } +test Tor.lns get "VirtualAddrNetworkIPv4 10.192.0.0/10\n" = { "VirtualAddrNetworkIPv4" = "10.192.0.0/10" } +test Tor.lns get "ServerTransportPlugin obfs3,obfs4 exec /usr/bin/obfs4proxy\n" = { "ServerTransportPlugin" = "obfs3,obfs4 exec /usr/bin/obfs4proxy" } +test Tor.lns get "HiddenServiceDir /var/lib/tor/hidden_service/\n" = { "HiddenServiceDir" = "/var/lib/tor/hidden_service/" } diff --git a/data/usr/share/augeas/lenses/tor.aug b/data/usr/share/augeas/lenses/tor.aug new file mode 100644 index 000000000..60208c101 --- /dev/null +++ b/data/usr/share/augeas/lenses/tor.aug @@ -0,0 +1,36 @@ +(* Tor lens for Augeas + +Author: James Valleroy + +About: Reference + Online Tor configuration manual: + https://www.torproject.org/docs/tor-manual.html.en + +About: License + This file is licensed under the LGPL v2+. + +About: Configuration files + This lens applies to /etc/tor/torrc. See . + +*) + + +module Tor = + +autoload xfm + +let eol = Util.eol + +let ws = /[ \t]/ +let kc = /[A-Za-z0-9_.,:*]/ +let vc = /[A-Za-z0-9_.,:*\/ ]/ +let keyname = kc+ +let val = /[[\/]*/ . kc . (vc* . /[]]*/ . vc* . kc . /[\/]*/)? + +let entry = [ key keyname . del ws+ " " . store val . eol ] + +let lns = (entry|Util.comment|Util.empty_dos)* + +let filter = (incl "/etc/tor/torrc") + +let xfm = transform lns filter diff --git a/plinth/modules/tor/__init__.py b/plinth/modules/tor/__init__.py index e12773198..0149e4a6a 100644 --- a/plinth/modules/tor/__init__.py +++ b/plinth/modules/tor/__init__.py @@ -23,13 +23,14 @@ import augeas from django.utils.translation import ugettext_lazy as _ import glob import itertools +import json from plinth import actions from plinth import action_utils from plinth import cfg from plinth import service as service_module from plinth.modules.names import SERVICES -from plinth.signals import domain_added +from plinth.signals import domain_added, domain_removed version = 1 @@ -72,20 +73,23 @@ def init(): is_external=True, enabled=is_enabled()) # Register hidden service name with Name Services module. - (hs_enabled, hs_hostname, hs_ports) = get_hs() + hs_info = get_hs() + hostname = hs_info['hostname'] + hs_virtports = [port['virtport'] for port in hs_info['ports']] - if is_enabled() and is_running() and hs_enabled and hs_hostname: + if is_enabled() and is_running() and \ + hs_info['enabled'] and hs_info['hostname']: hs_services = [] for service_type in SERVICES: - if str(service_type[2]) in hs_ports: + if str(service_type[2]) in hs_virtports: hs_services.append(service_type[0]) else: - hs_hostname = None + hostname = None hs_services = None domain_added.send_robust( sender='tor', domain_type='hiddenservice', - name=hs_hostname, description=_('Tor Hidden Service'), + name=hostname, description=_('Tor Hidden Service'), services=hs_services) @@ -98,6 +102,23 @@ def setup(helper, old_version=None): ['configure', '--apt-transport-tor', 'enable']) helper.call('post', socks_service.notify_enabled, None, True) helper.call('post', bridge_service.notify_enabled, None, True) + helper.call('post', update_hidden_service_domain) + + +def update_hidden_service_domain(status=None): + """Update HS domain with Name Services module.""" + if not status: + status = get_status() + + domain_removed.send_robust( + sender='tor', domain_type='hiddenservice') + + if status['enabled'] and status['is_running'] and \ + status['hs_enabled'] and status['hs_hostname']: + domain_added.send_robust( + sender='tor', domain_type='hiddenservice', + name=status['hs_hostname'], description=_('Tor Hidden Service'), + services=status['hs_services']) def is_enabled(): @@ -113,45 +134,30 @@ def is_running(): def get_status(): """Return current Tor status.""" output = actions.superuser_run('tor', ['get-ports']) - port_info = output.split('\n') - ports = {} - for line in port_info: - try: - (key, val) = line.split() - ports[key] = val - except ValueError: - continue + ports = json.loads(output) - (hs_enabled, hs_hostname, hs_ports) = get_hs() + hs_info = get_hs() + hs_services = [] + hs_virtports = [port['virtport'] for port in hs_info['ports']] + for service_type in SERVICES: + if str(service_type[2]) in hs_virtports: + hs_services.append(service_type[0]) return {'enabled': is_enabled(), 'is_running': is_running(), 'ports': ports, - 'hs_enabled': hs_enabled, - 'hs_hostname': hs_hostname, - 'hs_ports': hs_ports, + 'hs_enabled': hs_info['enabled'], + 'hs_status': hs_info['status'], + 'hs_hostname': hs_info['hostname'], + 'hs_ports': hs_info['ports'], + 'hs_services': hs_services, 'apt_transport_tor_enabled': is_apt_transport_tor_enabled()} def get_hs(): """Return hidden service status.""" output = actions.superuser_run('tor', ['get-hs']) - output = output.strip() - if output == '': - hs_enabled = False - hs_hostname = 'Not Configured' - hs_ports = '' - elif output == 'error': - hs_enabled = False - hs_hostname = 'Not available (Run Tor at least once)' - hs_ports = '' - else: - hs_enabled = True - hs_info = output.split() - hs_hostname = hs_info[0] - hs_ports = hs_info[1] - - return (hs_enabled, hs_hostname, hs_ports) + return json.loads(output) def get_augeas(): @@ -239,8 +245,7 @@ def diagnose(): results.extend(_diagnose_control_port()) output = actions.superuser_run('tor', ['get-ports']) - ports = [line.split() for line in output.splitlines()] - ports = {port_type: int(port) for port_type, port in ports} + ports = json.loads(output) results.append([_('Tor relay port available'), 'passed' if 'orport' in ports else 'failed']) diff --git a/plinth/modules/tor/templates/tor.html b/plinth/modules/tor/templates/tor.html index 9b6db3ed5..a81550b18 100644 --- a/plinth/modules/tor/templates/tor.html +++ b/plinth/modules/tor/templates/tor.html @@ -62,13 +62,19 @@ {% trans "Hidden Service" %} - {% trans "Port" %} + {% trans "Status" %} + {% trans "Ports" %} {{ status.hs_hostname }} - {{ status.hs_ports }} + {{ status.hs_status }} + + {% for service in status.hs_services %} + {{ service }} + {% endfor %} + diff --git a/plinth/modules/tor/views.py b/plinth/modules/tor/views.py index d041c7155..fc9f42f2b 100644 --- a/plinth/modules/tor/views.py +++ b/plinth/modules/tor/views.py @@ -27,8 +27,6 @@ from .forms import TorForm from plinth import actions from plinth.errors import ActionError from plinth.modules import tor -from plinth.modules.names import SERVICES -from plinth.signals import domain_added, domain_removed config_process = None @@ -115,22 +113,7 @@ def _collect_config_result(request): tor.socks_service.notify_enabled(None, status['enabled']) tor.bridge_service.notify_enabled(None, status['enabled']) - - # Update hidden service name registered with Name Services module. - domain_removed.send_robust( - sender='tor', domain_type='hiddenservice') - - if status['enabled'] and status['is_running'] and \ - status['hs_enabled'] and status['hs_hostname']: - hs_services = [] - for service in SERVICES: - if str(service[2]) in status['hs_ports']: - hs_services.append(service[0]) - - domain_added.send_robust( - sender='tor', domain_type='hiddenservice', - name=status['hs_hostname'], description=_('Tor Hidden Service'), - services=hs_services) + tor.update_hidden_service_domain(status) if not return_code: messages.success(request, _('Configuration updated.')) diff --git a/setup.py b/setup.py index cb0596a06..5bfb41160 100755 --- a/setup.py +++ b/setup.py @@ -214,9 +214,9 @@ setuptools.setup( ('/usr/share/man/man1', ['doc/plinth.1']), ('/etc/plinth', ['data/etc/plinth/plinth.config']), ('/usr/share/augeas/lenses', - ['data/usr/share/augeas/lenses/pagekite.aug']), + glob.glob('data/usr/share/augeas/lenses/*.aug')), ('/usr/share/augeas/lenses/tests', - ['data/usr/share/augeas/lenses/tests/test_pagekite.aug']), + glob.glob('data/usr/share/augeas/lenses/tests/test_*.aug')), ('/etc/plinth/modules-enabled', glob.glob(os.path.join('data/etc/plinth/modules-enabled', '*')))],