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

All Whonix configuration files provided are searched for the last known occurrence
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 in case the configuration files were modified due to a system update.

Qubes feature request: optional static IP addresses
https://github.com/QubesOS/qubes-issues/issues/1477

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}')

# 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=[
    '/usr/share/whonix-gw-network-conf/network_internal_ip.txt',
    '/etc/resolv.conf.anondist',
    '/etc/rinetd.conf.anondist',
    '/etc/tor/torrc',
    '/usr/local/etc/torrc.d/50_user.conf',
    '/usr/share/anon-gw-anonymizer-config/torrc.examples',
    '/home/user/.torchat/torchat.ini',
    '/home/user/.xchat2/xchat.conf',
    '/home/user/.config/hexchat/hexchat.conf',
    '/usr/lib/leaktest-workstation/simple_ping.py',
    '/usr/share/anon-apps-config/kioslaverc',
    '/usr/share/anon-torchat/.torchat/torchat.ini',
    '/usr/share/tor/tor-service-defaults-torrc.anondist',
]


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

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

    qubes_vm_type = subprocess.check_output(['qubesdb-read', '/qubes-vm-type']).rstrip()

    if qubes_vm_type == "TemplateVM":
        mode = 'template'
    elif os.path.exists('/usr/share/anon-gw-base-files/gateway'):
        mode = 'gateway'
    elif os.path.exists('/usr/share/anon-ws-base-files/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=''):
    '''Retrieve 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 reliable 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.
    '''
    try:
        if subprocess.check_output(['systemctl', 'is-active', 'tor@default']):
            # Restarting instead of reloading due to upstream Tor bug
            # https://trac.torproject.org/projects/tor/ticket/16161
            subprocess.call(['systemctl', 'restart', 'tor@default'])
    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')

    if last_ip_gateway:
        if whonix_mode() == 'gateway':
            try:
                current_ip_gateway = subprocess.check_output(['qubesdb-read', '/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:
                ## FIXME: 'qubesdb-read /qubes-ip' could fail if NetVM is set to 'none'.
                current_ip_local = subprocess.check_output(['qubesdb-read', '/qubes-ip']).rstrip()
                ## FIXME: 'qubesdb-read /qubes-gateway' could fail if NetVM is set to 'none'.
                current_ip_gateway = subprocess.check_output(['qubesdb-read', '/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:])
