"""
HE-2-4 Copyright (C) 2014 Liam Stanley
License: Eiffel Forum License, version 2
 - https://links.ml/FFa1
Website: https://liamstanley.io

NOTE: THIS IS FOR KVM BASED SERVERS ONLY.
  I'M NOT ADDING CUSTOM CHECKS TO SEE IF
  YOU'RE USING KVM.
"""

import subprocess
import os
import re
import time
from datetime import datetime

# Tunnelbroker IPv6
server_ipv6 = '2001:470:1f10:e55::1'
# Tunnelbroker IPv4
server_ipv4 = '184.105.253.14'

# Routed /64 or /48 (WITH suffix) (i.e. the ::/64 or the ::/48 at the end)
bind_to_ip = '2001:470:c280::/48'

# Number of IPv6 to bind (as ::<bind number>) starting from 5 (to be safe)
bind_count = 95 # 95 means the last one will be ::100.


# Ignore shit after this line or expect something to break. Yep I said it.

re_ipv6 = r'(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))'
re_ipv4 = r'^[0-9]{2,3}\.[0-9]{2,3}\.[0-9]{2,3}\.[0-9]{2,3}$'

def main():
    """ Do stuff here """

    # First we need to ensure the data is sanitized and the end user isn't dumb.
    global server_ipv6, server_ipv4, bind_to_ip, bind_count
    server_ipv6 = server_ipv6.split('/', 1)[0].strip()
    server_ipv4 = server_ipv4.strip()
    bind_count += 5

    if not re.match(re_ipv6, server_ipv6):
        output('error', 'server_ipv6 seems to be an incorrect IPv6 address. Exiting!')
        os._exit(1)

    if not re.match(re_ipv4, server_ipv4):
        output('error', 'server_ipv4 seems to be an incorrect IPv4 address. Exiting!')
        os._exit(1)

    if '::' not in bind_to_ip or '/' not in bind_to_ip:
        output('error', 'bind_to_ip seems to not have the netmask attached!. Exiting!')
        os._exit(1)

    ip, netmask = bind_to_ip.split('::', 1)

    if not re.match(re_ipv6, ip + '::'):
        output('error', 'bind_to_ip seems to be an incorrect IPv6 address. Exiting!')
        os._exit(1)

    if not re.match(r'^\/[0-9]{2,3}$', netmask):
        output('error', 'The netmask attached to bind_to_ip seems to be an incorrect netmask. Exiting!')
        os._exit(1)

    # Assume that the above is finally correct, and we can start attempting to bind

    # Delete the old tunnel in case it's up
    output('status', 'Taking down interface if it\'s already up...')
    cmd('ip tun del sit1', ignore_errors=True)

    # Bring up the interface
    cmd('ifconfig sit0 up')
    output('status', 'Bringing up a empty interface..')

    # Connect the 4v6 tunnel to the IPv4-protocoled server
    output('status', 'Adding connection to remote IPv4 tunneling server...')
    cmd('ifconfig sit0 inet6 tunnel ::{server_ipv4};'.format(server_ipv4=server_ipv4))

    # Bring up the interface that will hold all the sub-binded IPv6
    output('status', 'Bringing up IPv6 tunnel interface...')
    cmd('ifconfig sit1 up')

    calc = str(float(0.03) * float(bind_count-5))
    if '.' in calc:
        first, second = calc.split('.', 1)
        if len(second) > 2:
            second[0:2]
        calc = first + '.' + second

    output('status', 'Binding {} IPv6 addresses. Estimated execution time: {} seconds.'.format(bind_count-5, calc))
    # Loop through and add x amount of ips to bind to the above interface
    for x in range(bind_count):
        cmd('ifconfig sit1 inet6 add {ip}::{bind_number}{netmask}'.format(ip=ip, bind_number=x, netmask=netmask))
        time.sleep(0.02)

    output('status', 'Bound {} IPv6 addresses to {}...'.format(bind_count-5, ip))

    # Bind the above interface to the IPv6 tunnel server
    output('status', 'Adding connection to remote IPv6 tunneling server...')
    cmd('ifconfig sit1 inet6 add {server_ipv6}::{netmask}'.format(server_ipv6=server_ipv6.split('::', 1)[0], netmask=netmask))
    
    # Completion..
    output('status', 'And last and least, enabling routing...')
    cmd('route -A inet6 add ::/0 dev sit1')

    output('status', 'Success!')


def docstring():
    symbol = '*'
    lines = __doc__.strip().split('\n')
    largest = 0
    for line in lines:
        if len(line) > largest:
            largest = len(line)
    outer = (largest + (1 * 4)) * symbol
    output('', outer)
    for line in lines:
        tmp = symbol + (1 * ' ') + line
        sidedif = (largest + (1 * 4)) - len(tmp) - 1
        tmp += ' ' * sidedif + symbol
        output('', tmp)
    output('', outer)
    output('INIT', 'Initializing IPv6 bind script')


def cmd(command, ignore_errors=False, ignore_stdout=False):
    """
        Execute some command that may or may not completely fuck up your system.

        Jk's.
    """
    p = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)
    (output, err) = p.communicate()
    if err:
        output('error', str(err))
        output('error', 'Exiting!')
        os._exit(1)
    return str(output), str(err)


def output(prefix, text):
    """ Output text with a prefix. (sexify it) """
    tn = datetime.now().strftime('%H:%M:%S')
    if prefix:
        print('[{}] [{}] {}'.format(tn, prefix, text))
    else:
        print('[{}]   {}'.format(tn, text))


if __name__ == '__main__':
    try:
        docstring()
        main()
    except KeyboardInterrupt:
        output('request', 'Requested to exit. You do not have to be mean!')
        os._exit(0)
    except Exception as e:
        output('error', str(e))
        output('error', 'Exiting!')
        os._exit(1)
