#!/usr/bin/python3
# -*- mode: python -*-
#
# This file is part of Plinth.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

"""
Configuration helper for the Tor service
"""

import argparse
import os
import subprocess

SERVICE_CONFIG = '/etc/default/tor'
TOR_CONFIG = '/etc/tor/torrc'


def parse_arguments():
    """Return parsed command line arguments as dictionary"""
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')

    # Get whether Tor is running
    subparsers.add_parser('is-running', help='Get whether Tor is running')

    # Start Tor and enable service
    subparsers.add_parser('start', help='Start Tor service')

    # Stop Tor and disable service
    subparsers.add_parser('stop', help='Stop Tor service')

    # Get currently configured Tor hidden service information
    subparsers.add_parser('get-hs', help='Get hidden service')

    # Enable Tor hidden service
    subparsers.add_parser('enable-hs', help='Enable hidden service')

    # Disable Tor hidden service
    subparsers.add_parser('disable-hs', help='Disable hidden service')

    return parser.parse_args()


def subcommand_is_running(_):
    """Get whether Tor is running"""
    try:
        output = subprocess.check_output(['service', 'tor', 'status'])
    except subprocess.CalledProcessError:
        # If Tor is not running we get a status code != 0 and a
        # CalledProcessError
        print('no')
    else:
        running = False
        for line in output.decode().split('\n'):
            if 'Active' in line and 'running' in line:
                running = True
                break

        print('yes' if running else 'no')


def subcommand_start(_):
    """Start Tor and enable it as service"""
    set_tor_service(enable=True)
    subprocess.call(['service', 'tor', 'start'])


def subcommand_stop(_):
    """Stop Tor and disable it as service"""
    set_tor_service(enable=False)
    subprocess.call(['service', 'tor', 'stop'])


def subcommand_get_hs(_):
    """Print currently configured Tor hidden service information"""
    print(get_hidden_service())


def subcommand_enable_hs(_):
    """Enable Tor hidden service"""
    if get_hidden_service():
        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)

    subprocess.call(['service', 'tor', 'restart'])


def subcommand_disable_hs(_):
    """Disable Tor hidden service"""
    if not get_hidden_service():
        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)

    subprocess.call(['service', 'tor', 'restart'])


def set_tor_service(enable):
    """Enable/disable Tor service; enable: boolean"""
    newline = 'RUN_DAEMON="yes"\n' if enable else 'RUN_DAEMON="no"\n'

    with open(SERVICE_CONFIG, 'r') as file:
        lines = file.readlines()
        for index, line in enumerate(lines):
            if line.startswith('RUN_DAEMON'):
                lines[index] = newline
                break

    with open(SERVICE_CONFIG, 'w') as file:
        file.writelines(lines)


def get_hidden_service():
    """Return a string with configured Tor hidden service information"""
    hs_dir = None
    hs_ports = []

    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])

    if not hs_dir:
        return ''

    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)


def main():
    """Parse arguments and perform all duties"""
    arguments = parse_arguments()

    subcommand = arguments.subcommand.replace('-', '_')
    subcommand_method = globals()['subcommand_' + subcommand]
    subcommand_method(arguments)


if __name__ == '__main__':
    main()
