#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright 2009-2010 TUBITAK/UEKAE
# Licensed under the GNU General Public License, version 2.
# See the file http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
#
#
#
# A migration script to transform COMAR based network profiles into NetworkManager based network profiles.
#
# The main purpose of the script is help Pardus 2009 users to be able to use their network profiles in Pardus 2011
# without any effort when they upgrade their system.
#
# A few words about how stuff works. COMAR based network manager keeps profile settings under /etc/network with two
# files: net_tools which includes ethernet profile settings and wireless_tools which includes wireless profile settings.
# On the other hand, NetworkManager keeps each profile settings in an individual file under /etc/NetworkManager/system-connections.
#
# This script simply reads your old profile files and try to create a new profile file compatible with NetworkManager for each profile.
#
#


import os
import ConfigParser


default_nameservers = []
default_resolv_conf_file = "/etc/resolv.default.conf"
lan_config_file  = "/etc/network/net_tools"
wlan_config_file  = "/etc/network/wireless_tools"
migration_sign = "/etc/network/.migrated"
NetworkManager_conf_dir = "/etc/NetworkManager/system-connections"

def is_migrated_before():
    """Make this tool run once; so after migration, touch a file named '.migrated' 
    under old network manager profiles dir. Check it first and then try to migrate profiles.
    """

    global migration_sign
    if os.path.exists(migration_sign):
        return True
    else:
        return False

def get_default_nameservers():
    """Read once default name servers in resolve.default.conf, if 'name_mode' in
    given profile was set to default, supply these values in the new profile file
    """
    global default_nameservers
    if len(default_nameservers) == 0:
        if os.path.exists(default_resolv_conf_file):
            with open(default_resolv_conf_file) as f:
                nameservers_file = f.readlines()

            for line in nameservers_file:
                if not line.startswith("#") and line.startswith("nameserver"):
                    ns = line.split()[-1]
                    default_nameservers.append(ns)

        #If we can't find default.resolv.conf, supply default Pardus DNS servers
        else:
            default_nameservers = ["193.140.100.210", "193.140.100.215", "193.140.100.220"]

    return default_nameservers


class PardusNetworkProfile:
    """Represents COMAR based network profiles that we have been using in Pardus' network
    manager. We have two types of network profiles specified: wired or wireless
    """

    def __init__(self, name, connection_type, settings):

        self.profile_name = name
        self.connection_type = connection_type
        self.device = "none"
        self.net_mode = "none"
        self.name_mode = "none"
        self.state = "none"
        self.name_server = "none"
        self.net_address = "none"
        self.net_mask = "none"
        self.net_gateway = "none"
        self.remote = "none"
        self.auth = "none"
        self.auth_password = "none"

        self.set_attributes(settings)

    def set_attributes(self, settings):
        """We receive profile settings as dictionary type in 'settings'
        Map each key-value pairs for each object.
        """

        for key, value in settings.items():
            self.__dict__[key] = value

    def get_profile_name(self):
        return self.profile_name

    def get_connection_type(self):
        return self.connection_type

    def get_device(self):
        return self.device

    def get_net_mode(self):
        return self.net_mode

    def get_name_mode(self):
        return self.name_mode

    def get_state(self):
        return self.state

    def get_name_server(self):
        return self.name_server

    def get_net_address(self):
        return self.net_address

    def get_net_mask(self):
        return self.net_mask

    def get_net_gateway(self):
        return self.net_gateway

    def get_remote(self):
        return self.remote

    def get_auth(self):
        return self.auth

    def get_auth_password(self):
        return self.auth_password


class NetworkManagerProfile:
    """Represents network profiles used in NetworkManager. In NetworkManager, 
    settings are kept in a seperate file for each profile.
    """

    def __init__(self, name, pardus_profile):
        """Each section is kept in corresponding class attribute"""

        self.cfg = ConfigParser.ConfigParser()
        self.connection = Connection(pardus_profile)
        self.ipv4 = IpV4(pardus_profile)
        self.ipv6 = IpV6(pardus_profile)
        self._802_3_ethernet = self.set_802_3_ethernet(pardus_profile)
        self._802_11_wireless = self.set_802_11_wireless(pardus_profile)
        self._802_11_wireless_security = self.set_802_11_wireless_security(pardus_profile)

        self.create_config()

    def set_802_3_ethernet(self, pardus_profile):
        """If this is a wired (802-3-ethernet) profile, set _802_3_ethernet 
        attribute.
        """

        if pardus_profile.connection_type == "802-3-ethernet":
            return  _802_3_Ethernet(pardus_profile)
        else:
            return "none"

    def set_802_11_wireless(self, pardus_profile):
        """If this is a wireless (802-11-wirelesss) profile, set 
        _802_11_wireless attribute."""
        if pardus_profile.connection_type == "802-11-wireless":
            return  _802_11_Wireless(pardus_profile)
        else:
            return "none"

    def set_802_11_wireless_security(self, pardus_profile):
        """If the wireless connection has any security restrictions, create a 
        802-11-wireless-security section with corresponding values.
        """

        if pardus_profile.get_connection_type() ==  "802-11-wireless": #Check if it is a wlan profile
            if pardus_profile.get_auth() == "none":
                self._802_11_wireless.security = "none"
                return "none"
            else:
                self._802_11_wireless.security = "802-11-wireless-security"
                return _802_11_Wireless_Security(pardus_profile)
        else:
            return "none"

    def create_config(self):
        """Create sections and options in the prfoile's config file by calling
        each options corresponding method.
        """

        #FIXME: Try to do it over loops ie. self[attr].set_config()
        for attr, value in self.__dict__.items():
            if attr == "connection":
                self.connection.set_config(self.cfg)
            if attr == "ipv4":
                self.ipv4.set_config(self.cfg)
            if attr == "ipv6":
                self.ipv6.set_config(self.cfg)
            if attr == "_802_3_ethernet" and not value == "none":
                self._802_3_ethernet.set_config(self.cfg)
            if attr == "_802_11_wireless" and not value == "none":
                self._802_11_wireless.set_config(self.cfg)
            if attr == "_802_11_wireless_security" and not value == "none":
                self._802_11_wireless_security.set_config(self.cfg)

    def write_config(self):
        """Write settings to profile file"""

        #Before writing to file we must convert underscores to dashes, moreover _id must be written as id, and _type as type

        if not os.path.exists(NetworkManager_conf_dir):
            os.makedirs(NetworkManager_conf_dir, mode=0755)

        profile_path = os.path.join("%s" % NetworkManager_conf_dir, self.connection._id)
        with open(profile_path, "wb") as configfile:
            self.cfg.write(configfile)

    def set_ownership(self):
        """NetworkManager is smart enough to understand whether a configuration file is valid 
        by checking its permissions"""

        os.chmod(os.path.join("%s" % NetworkManager_conf_dir, self.connection._id), 0600)


class Connection:
    """Instances of Connection class represent essential properties of a network connection to identify itself."""

    def __init__(self, pardus_profile):
        self.name = "connection"
        self._id = pardus_profile.get_profile_name()
        self.uuid = self.set_uuid(device = pardus_profile.get_device())
        self._type = pardus_profile.get_connection_type()
        self.autoconnect = self.set_autoconnect(pardus_profile)
        self.timestamp = "none"
        self.read_only = "false"

    def set_autoconnect(self, profile):
        """Set profile activation at startup"""

        if str(profile.get_state()) == "down":
            return "false"
        else:
            return "none"   #NM set this 'true' as default if we dont set anything

    def set_uuid(self, device):
        """Generate random type UUID"""
        import uuid

        return str(uuid.uuid4())

    def set_config(self, cfg):
        """One single config file will be used to write settings"""

        cfg.add_section(self.name)
        for attr, value in self.__dict__.items():
            if value not in ["false", "none"] and  attr != "name":
                #Before creating config file _id must be id, and _type must be type
                if attr == "_id" or attr == "_type" : attr = attr.split("_")[-1]
                #There isnt any underscore in config options, replace them with dashes if found any
                attr = attr.replace("_", "-")
                cfg.set(self.name, attr, value)


class IpV4:
    """Instances of IpV4 class represent IPv4 based properties of a connection."""

    def __init__(self, pardus_profile):
        self.name = "ipv4"
        self.dns_search = "none"
        self.routes = "none"
        self.ignore_auto_routes = "false"
        self.ignore_auto_dns = "false"
        self.dhcp_client_id = "none"
        self.dhcp_send_hostname = "false"
        self.dhcp_hostname = "none"
        self.never_default = "false"
        self.method = pardus_profile.get_net_mode() # auto or manual, same as in NM
        self.addresses1 = self.set_addresses(pardus_profile)    # NM uses array of IPv4 address structures. We have only one for each iface
        self.dns = self.set_dns(pardus_profile)

    def set_dns(self, pardus_profile):
        """Decide whether to use default, custom or auto (DHCP assigned) nameservers"""

        if pardus_profile.get_name_mode() == "default":
            default_nameservers = ";".join( get_default_nameservers())
            default_nameservers = default_nameservers + ";" # Make sure addresses end with ';'
            self.ignore_auto_dns = "true"
            return str(default_nameservers)
        elif pardus_profile.get_name_mode() == "custom":
            name_server = str(pardus_profile.get_name_server())
            name_server = name_server + ";"
            self.ignore_auto_dns = "true"
            return str(name_server)
        else:
            # Nothing done in auto option
            return "none"

    def set_addresses(self, pardus_profile):
        """Set network addresses from given settings"""

        addresses = []
        if self.method == "manual":
            net_mask = self.calculate_prefix(pardus_profile.get_net_mask())
            addresses.append(str(pardus_profile.get_net_address()))
            addresses.append(str(net_mask))
            addresses.append(str(pardus_profile.get_net_gateway()))
            addresses = ";".join(addresses)
            addresses = addresses + ";"     # Make sure addresses end with ';'
            return addresses
        else:
            return "none"

    def decimal2binary(self, n):
        """Convert decimal octet value to binary format"""

        octet = ["0", "0", "0", "0", "0", "0", "0", "0"]
        index = 0
        if n < 0 or n > 255:
            raise ValueError, "Octet value must be between [0-255]"
        if n == 0: 
            return "".join(octet)
        while n > 0:
            octet[index] = str((n % 2))
            index += 1
            n = n >> 1
        octet.reverse()
        return "".join(octet)

    def calculate_prefix(self, net_mask):
        """Convert netmask value to CIDR prefix type which is between [1-32] as told in NM spec
            See http://mail.gnome.org/archives/networkmanager-list/2008-August/msg00076.html"""

        octets = net_mask.split(".")
        octet_in_binary = []
        netmask_value = 0
        for octet in octets:
            ret = self.decimal2binary(int(octet))
            octet_in_binary.append(ret)
        for i in "".join(octet_in_binary):
            if int(i) == 1 : netmask_value += 1
        return netmask_value

    def set_config(self, cfg):
        """One single config file will be used to write settings"""

        cfg.add_section(self.name)
        for attr, value in self.__dict__.items():
            if value not in ["false", "none"] and  attr != "name":
                attr = attr.replace("_", "-")
                cfg.set(self.name, attr, value)


class IpV6:
    """Instances of IpV6 class represent IPv6 based properties of a connection."""

    def __init__(self, pardus_profile):
        self.name = "ipv6"
        self.method = "none"
        self.dns = "none"
        self.dns_search = "none"
        self.addresses = "none"
        self.routes = "none"
        self.ignore_auto_routes = "false"
        self.ignore_auto_dns = "false"
        self.dhcp_client_id = "none"
        self.dhcp_send_hostname = "none"
        self.dhcp_hostname = "none"
        self.never_default = "false"

        self.set_method()

    def set_method(self):
        """Ignoring by default for nowadays"""
        self.method = "ignore"

    def set_config(self, cfg):
        """One single config file will be used to write settings"""

        cfg.add_section(self.name)
        for attr, value in self.__dict__.items():
            if value not in ["false", "none"] and  attr != "name":
                attr = attr.replace("_", "-")
                cfg.set(self.name, attr, value)

class _802_3_Ethernet:
    """Instances of _802_3_Ethernet class represent options related with ethernet device."""

    def __init__(self, pardus_profile):
        self.name = "802-3-ethernet"
        self.port = "none"
        self.speed = "none" #0
        self.duplex = "full"
        self.auto_negotiate = "false"
        self.mac_address = self.set_mac_address(pardus_profile.get_device())
        self.mtu = "none" #0

    def set_mac_address(self, iface):
        """Return MAC addresses of given interface on the machine"""
        if os.path.exists("/sys/class/net/%s" % iface):
            return open("/sys/class/net/%s/address" % iface).read().strip()
        return "none"

    def set_config(self, cfg):
        """One single config file will be used to write settings"""

        cfg.add_section(self.name)
        for attr, value in self.__dict__.items():
            if value not in ["false", "none"] and  attr != "name":
                attr = attr.replace("_", "-")
                cfg.set(self.name, attr, value)


class _802_11_Wireless:
    """Instances of _802_11_Wireless class represent options related with wireless device."""

    def __init__(self, pardus_profile):
        self.name = "802-11-wireless"
        self.ssid = self.set_ssid(pardus_profile)
        self.band = "none"
        self.channel = "0"
        self.bssid = "none"
        self.rate = "0"
        self.tx_power = "0"
        self.mtu = "0"
        self.seen_bssids = "none"
        self.security = "none"
        self.mode = self.set_mode(pardus_profile)
        self.mac_address = self.set_mac_address(pardus_profile.get_device())

    def set_mac_address(self, iface):
        """Return MAC addresses of given interface on the machine"""
        if os.path.exists("/sys/class/net/%s" % iface):
            return open("/sys/class/net/%s/address" % iface).read().strip()
        return "none"

    def set_mode(self, pardus_profile):
        """One of 'infrastructure' or 'adhoc'. If blank, infrastructure is assumed"""

        #TODO: How to determine mode (adhoc or infrastructure) from old profile settings
        return "infrastructure"

    def set_ssid(self, pardus_profile):
        """Set ssid value"""

        if pardus_profile.connection_type == "802-11-wireless":
            return str(pardus_profile.get_remote())
        else:
            return "none"

    def set_config(self, cfg):
        """One single config file will be used to write settings"""

        cfg.add_section(self.name)
        for attr, value in self.__dict__.items():
            if value not in ["false", "none", "0"] and  attr != "name":
                attr = attr.replace("_", "-")
                cfg.set(self.name, attr, value)


class _802_11_Wireless_Security:
    """Instances of _802_11_Wireless_Security class represent authentication properties and operations 
    used in order to be included in networks requiring them.
    """

    def __init__(self, pardus_profile):
        self.name = "802-11-wireless-security"
        self.key_mgmt = "none"
        self.wep_tx_keyidx = "0"
        self.auth_alg = "none"
        self.proto = "none"
        self.pairwise = "none"
        self.group = "none"
        self.leap_username = "none"
        self.wep_key0 = "none"
        self.wep_key1 = "none"
        self.wep_key2 = "none"
        self.wep_key3 = "none"
        self.psk = "none"
        self.leap_password = "none"
        self.wep_key_type = "0"

        self.set_up_wireless_security(pardus_profile)

    def set_up_wireless_security(self, pardus_profile):
        """WEP, WPA or none type security based operations"""

        if pardus_profile.get_auth() in ["wep", "wepascii"]:
            self.set_wep(pardus_profile)
        elif pardus_profile.get_auth() == "wpa-psk":
            self.set_wpa(pardus_profile)
        else:
            return

    def set_wpa(self, pardus_profile):
        """Set up WPA based networks"""

        self.key_mgmt = "wpa-psk"
        self.psk = str(pardus_profile.get_auth_password())

    def set_wep(self, pardus_profile):
        """Set up WEP based networks"""

        self.auth_alg = "open" #TODO: or 'shared' ??
        self.key_mgmt = "None" # Which stands for WEP based key management
        self.wep_key0 = str(pardus_profile.get_auth_password()) # Default index
        self.wep_key_type = "1" # Interpret WEP keys as hex or ascii keys


    def set_config(self, cfg):
        """One single config file will be used to write settings"""

        cfg.add_section(self.name)
        for attr, value in self.__dict__.items():
            if value not in ["false", "none", "0"] and  attr != "name":
                attr = attr.replace("_", "-")

                # key-mgmt=none is a mandatory assignment for WEP based
                # configurations. As we set all class members' default value
                # to 'none', and then filter to set config file with non-none
                # values, key-mgmt is set to 'None' intentionally to pass that
                # filtering, and its value is set later on by lowercasing to
                # hack around this situation.
                if attr == "key-mgmt":
                    value = value.lower()
                cfg.set(self.name, attr, value)


class Migrator:
    """Read COMAR based network profiles we have been using in Pardus' network manager and 
    transform them into NetworkManager profile type.
    """

    def __init__(self):

        self.pardus_profiles = []
        self.network_manager_profiles = []
        self.lan_config_path = lan_config_file
        self.wlan_config_path = wlan_config_file
        self.read_pardus_profiles()

    def read_pardus_profiles(self):
        """Read wired/wireless profile settings, create PardusNetworkProfile 
        object for each one, and store them in a list.
        """

        self.lan_config = ConfigParser.ConfigParser()
        self.lan_config.read(self.lan_config_path)
        connection_type = "802-3-ethernet"
        for section in self.lan_config.sections():
            lan_settings = {}
            for option in self.lan_config.options(section):
                if option == "device":
                    #To strip device name from long device string
                    lan_settings[option] = self.lan_config.get(section, option).split("_")[-1]
                else:
                    lan_settings[option] = self.lan_config.get(section, option)
            p = PardusNetworkProfile(section, connection_type, lan_settings)
            self.pardus_profiles.append(p)

        self.wlan_config = ConfigParser.ConfigParser()
        self.wlan_config.read(self.wlan_config_path)
        connection_type = "802-11-wireless"
        for section in self.wlan_config.sections():
            wlan_settings = {}
            for option in self.wlan_config.options(section):
                if option == "device":
                    wlan_settings[option] = self.wlan_config.get(section, option).split("_")[-1]
                else:
                    wlan_settings[option] = self.wlan_config.get(section, option)
            p = PardusNetworkProfile(section, connection_type, wlan_settings)
            self.pardus_profiles.append(p)

    def transform_profiles(self):
        """Convert Pardus' network profiles to NetworkManager profiles"""

        if len(self.pardus_profiles) > 0:  # Make sure we have some profiles to migrate
            for profile in self.pardus_profiles:
                network_manager_profile = NetworkManagerProfile(profile.get_profile_name, profile)
                self.network_manager_profiles.append(network_manager_profile)

    def write_network_manager_profiles(self):
        """Create profile file for each NetworkManager profile and change ownerships to 0600"""
        
        if len(self.network_manager_profiles) > 0:  # Make sure we have some profiles to migrate
            for profile in self.network_manager_profiles:
                profile.write_config()
                profile.set_ownership()
        with open(migration_sign, "w") as f:
            pass


if __name__ == "__main__":

    print "DEBUG: Checking whether network profiles migrated..."
    if not is_migrated_before():
        m = Migrator()
        m.transform_profiles()
        m.write_network_manager_profiles()
        print "DEBUG: Network profiles migration completed successfully!"
    else:
        print "DEBUG: Network profiles migration seems already done before. Nothing done this time!"

