#!/usr/bin/python2 -O
# -*- coding: utf-8 -*-`
# vim: set ts=4 sw=4 sts=4 et :
'''
replace-ips - Search and repalce IP address in specified files.

All Whonix configuration files provided are searched for the last known occurance
of IP address that was used and replaced with the current IP address provided by
Qubes.

Initially the known IP addresses are 10.152.152.10 and 10.152.152.11 as defaults
in Whonix configuration files.  They are also checked each time this module is
run incase the configuration files were modified due to a system update.

This file is part of Qubes+Whonix.
Copyright (C) 2014 - 2015 Jason Mehring <nrgaway@gmail.com>
License: GPL-2+
Authors: Jason Mehring

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  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 General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
'''

import os
import sys
import re
import subprocess

WHONIX_IP_GATEWAY = '/var/cache/qubes-whonix/whonix-ip-gateway'
WHONIX_IP_LOCAL = '/var/cache/qubes-whonix/whonix-ip-local'
IP_ADDRESS = re.compile(r'(?:[0-9]{1,3}\.){3}[0-9]{1,3}')

# Do not modify any of the following files:
#
# They do not need to be replaced since they already have an additional
# configuration file that overrides it.
#
#   /etc/cpfpy.d/30_controlportfilt_default
#   /etc/sdwdate.d/31_anon_dist_stream_isolation_plugin
#   /etc/uwt.d/30_uwt_default
#   /etc/whonix_firewall.d/30_default

# This is a list of all Whonix files that contain IP addresses that will
# be searched and replaces with the current assigned IP address
FILES=[
    '/etc/apt/apt.conf.d/90whonix',
    '/etc/cpfpy.d/40_qubes',
    '/etc/network/interfaces.whonix',
    '/etc/resolv.conf',
    '/etc/resolv.conf.anondist',
    '/etc/rinetd.conf.anondist',
    '/etc/sdwdate.d/40_qubes_stream_isolation_plugin',
    '/etc/tor/torrc.examples',
    '/etc/uwt.d/40_qubes',
    '/etc/whonix_firewall.d/40_qubes',
    '/home/user/.torchat/torchat.ini',
    '/home/user/.xchat2/xchat.conf',
    '/usr/bin/update-torbrowser',
    '/usr/bin/uwt',
    '/usr/bin/whonix_firewall',
    '/usr/lib/anon-shared-helper-scripts/tor_bootstrap_check.bsh',
    '/usr/lib/anon-ws-disable-stacked-tor/torbrowser.sh',
    '/usr/lib/leaktest-workstation/simple_ping.py',
    '/usr/lib/whonixcheck/preparation',
    '/usr/share/anon-kde-streamiso/share/config/kioslaverc',
    '/usr/share/anon-torchat/.torchat/torchat.ini',
    '/usr/share/tor/tor-service-defaults-torrc.anondist',
    '/usr/share/xchat-improved-privacy/.xchat2/xchat.conf',
]


def whonix_mode():
    '''Determine Whonix mode.

    Can be either gateway, workstation, tempalte or unknown
    '''
    mode = None

    if os.path.exists('/var/run/qubes-service/whonix-template'):
        mode = 'template'
    elif os.path.exists('/var/run/qubes-service/whonix-gateway'):
        mode = 'gateway'
    elif os.path.exists('/var/run/qubes-service/whonix-workstation'):
        mode = 'workstation'

    return mode


def is_immutable(filename):
    '''Determine if file has been set immutable.'''
    filename =  os.path.abspath(filename)
    lsattr = subprocess.check_output(['lsattr', filename])
    return lsattr[4] ==  'i'


def replace_ip(ips, current_ip, files, ip_file):
    '''Searches and replaces IP adresss of provided files.

    ips:
        List of IP addresses to replace.

    current_ip:
        IP replacement address

    files:
        List of files to search.  The list must contain full pathnames.

    ip_files:
        Full path to filename used to store last known value if IP address.  The
        `current_ip` is stored in this file and used the next time this module
        is executed.
    '''
    replaced = False
    for filename in files:
        if os.path.exists(filename):
            try:
                with open(filename, 'r') as infile:
                    text = infile.read()
            except (IOError):
                return False

            match = text
            for last_ip in ips:
                match = re.sub(r'(?m){0}'.
                               format(re.escape(last_ip)),
                               current_ip,
                               match)

                match = re.sub(r'(?m){0}[.]0'.
                               format(re.escape(last_ip.rsplit('.', 1)[0])),
                               current_ip.rsplit('.', 1)[0] + '.0',
                               match)

            if text !=  match:
                try:
                    immutable = False
                    if is_immutable(filename):
                        subprocess.call(['chattr', '-i', os.path.abspath(filename)])

                    with open(filename, 'w') as outfile:
                        outfile.writelines(match)

                    replaced = True
                except (IOError):
                    return False

    if replaced:
        try:
            with open(ip_file, 'w') as outfile:
                outfile.writelines(current_ip)
            replaced = True
        except (IOError):
            return False

    return replaced


def get_ip_address(filename, default=''):
    '''Retreive an IP address from a file.

    The last know IP address is saved in a file so it can be referenced and used
    the next time system is booted to assist in a relaiable search and replace.
    '''
    if os.path.exists(filename):
        try:
            with open(filename, 'r') as infile:
                ip_address = infile.read()
            if IP_ADDRESS.match(ip_address):
                return ip_address.rstrip()
        except (IOError):
            return ''
    return default


def maybe_reload_tor():
    '''Reload Tor's configuration files if Tor is currently active and not
    disabled.
    '''
    torrc = '/etc/tor/torrc'
    if os.path.exists(torrc):
        try:
            with open(torrc, 'r') as infile:
                text = infile.read()
        except (IOError):
            return

    if re.search(r'^DisableNetwork 0$', text,  re.MULTILINE):
        try:
            if subprocess.check_output(['systemctl', 'is-active', 'tor']):
                # Restarting instead of reloading due to upstream Tor bug
                # https://trac.torproject.org/projects/tor/ticket/16161
                subprocess.call(['systemctl', 'restart', 'tor'])
        except (subprocess.CalledProcessError):
            pass


def main(args):
    last_ip_gateway = get_ip_address(WHONIX_IP_GATEWAY, '10.152.152.10')
    last_ip_local = get_ip_address(WHONIX_IP_LOCAL, '10.152.152.11')

    # Qubes R3
    if os.path.exists('/usr/bin/qubesdb-read'):
        qubesdb_read = 'qubesdb-read'
        prefix = '/'

    # Qubes R2
    else:
        qubesdb_read = 'xenstore-read'
        prefix = ''

    if last_ip_gateway:
        if whonix_mode() == 'gateway':
            try:
                current_ip_gateway = subprocess.check_output([qubesdb_read, prefix + 'qubes-netvm-gateway']).rstrip()
            except (OSError, subprocess.CalledProcessError):
                return

            if IP_ADDRESS.match(current_ip_gateway):
                ips_to_replace = [last_ip_gateway, '10.152.152.10',  '10.152.152.11']
                if replace_ip(ips_to_replace, current_ip_gateway, FILES, WHONIX_IP_GATEWAY):
                    maybe_reload_tor()

        if whonix_mode() == 'workstation' and last_ip_local:
            try:
                current_ip_local = subprocess.check_output([qubesdb_read, prefix + 'qubes-ip']).rstrip()
                current_ip_gateway = subprocess.check_output([qubesdb_read, prefix + 'qubes-gateway']).rstrip()
            except (OSError, subprocess.CalledProcessError):
                return

            if IP_ADDRESS.match(current_ip_local) and IP_ADDRESS.match(current_ip_gateway):
                ips_to_replace = [last_ip_local, '10.152.152.11']
                replace_ip(ips_to_replace, current_ip_local, FILES, WHONIX_IP_LOCAL)

                ips_to_replace = [last_ip_gateway, '10.152.152.10']
                replace_ip(ips_to_replace, current_ip_gateway, FILES, WHONIX_IP_GATEWAY)


if __name__ == "__main__":
    main(sys.argv[1:])
