# -*- coding: utf-8 -*-
# Copyright (C) 2014 the2nd <the2nd@otpme.org>
import os
import socket

try:
    if os.environ['OTPME_DEBUG_MODULE_LOADING'] == "True":
        msg = _("Loading module: {module}")
        msg = msg.format(module=__name__)
        print(msg)
except:
    pass

from otpme.lib import stuff
from otpme.lib import config
from otpme.lib import backend
from otpme.lib import filetools
from otpme.lib import multiprocessing

from otpme.lib.exceptions import *

logger = config.logger

REGISTER_BEFORE = []
REGISTER_AFTER = []

def host_data_getter(self):
    """ Host data (e.g. cert, key, secret, CRLs etc.). """
    from otpme.lib.multiprocessing import host_data
    # Make sure host data is (re)loaded (e.g. if cache was down).
    try:
        host_data['name']
    except:
        try:
            update_data()
        except Exception as e:
            if not config.realm_join:
                log_msg = _("Failed to load host data: {e}", log=True)[1]
                log_msg = log_msg.format(e=e)
                logger.critical(log_msg)
                config.raise_exception()
    return host_data

def register():
    multiprocessing.register_shared_dict("host_data")
    config.register_property(name="host_data", getx=host_data_getter)

def get_ssl_file_perms():
    # Realm users group may not exist (e.g. on realm init)
    try:
        stuff.group_exists(config.realm_users_group)
        realm_users_group = config.realm_users_group
    except:
        realm_users_group = config.group

    files = {
            config.ssl_cert_file    : {
                                        'file_owner'    : config.user,
                                        'file_group'    : realm_users_group,
                                        'file_mode'     : 0o640,
                                    },
            config.ssl_key_file    : {
                                        'file_owner'    : config.user,
                                        'file_group'    : realm_users_group,
                                        'file_mode'     : 0o640,
                                    },
            config.ssl_ca_file    : {
                                        'file_owner'    : config.user,
                                        'file_group'    : realm_users_group,
                                        'file_mode'     : 0o640,
                                    },
            config.ssl_site_cert_file    : {
                                        'file_owner'    : config.user,
                                        'file_group'    : realm_users_group,
                                        'file_mode'     : 0o640,
                                    },
            config.host_key_file    : {
                                        'file_owner'    : config.user,
                                        'file_group'    : "root",
                                        'file_mode'     : 0o600,
                                    },
        }
    return files

def get_file_owner_group(file):
    ssl_files_data = get_ssl_file_perms()
    try:
        file_data = ssl_files_data[file]
    except KeyError:
        msg = _("Missing file permissions: {file}")
        msg = msg.format(file=file)
        raise OTPmeException(msg)
    try:
        file_owner = file_data['file_owner']
    except KeyError:
        msg = _("Missing owner for file: {file}")
        msg = msg.format(file=file)
        raise OTPmeException(msg)
    try:
        file_group = file_data['file_group']
    except KeyError:
        msg = _("Missing group for file: {file}")
        msg = msg.format(file=file)
        raise OTPmeException(msg)
    try:
        file_mode = file_data['file_mode']
    except KeyError:
        msg = _("Missing mode for file: {file}")
        msg = msg.format(file=file)
        raise OTPmeException(msg)
    return file_owner, file_group, file_mode

def set_ssl_file_perms():
    files = get_ssl_file_perms()
    for file in files:
        file_owner, file_group, file_mode = get_file_owner_group(file)
        filetools.set_fs_ownership(path=file, user=file_owner, group=file_group)
        filetools.set_fs_permissions(path=file, mode=file_mode)

def update_ssl_files(host_cert=None, host_key=None,
    ca_data=None, site_cert=None, host_auth_key=None):
    """ Update SSL cert/key files. """
    # Create cert file if it does not exist.
    if host_cert:
        file_owner, file_group, file_mode = get_file_owner_group(config.ssl_cert_file)
        cert_dir = os.path.dirname(config.ssl_cert_file)
        if not os.path.exists(cert_dir):
            if not config.handle_files_dirs:
                msg = _("Unable to write SSL cert: No such file or directory: {cert_dir}")
                msg = msg.format(cert_dir=cert_dir)
                raise OTPmeException(msg)
            try:
                filetools.create_dir(path=cert_dir,
                                    user=config.user,
                                    group=config.group,
                                    mode=0o775)
            except IOError as e:
                if e.errno != e.errno.EACCES:
                    raise
        try:
            filetools.create_file(path=config.ssl_cert_file,
                                    content=host_cert,
                                    user=file_owner,
                                    group=file_group,
                                    mode=file_mode)
        except IOError as e:
            if e.errno != e.errno.EACCES:
                raise

    # Create key file if it does not exist.
    if host_key:
        file_owner, file_group, file_mode = get_file_owner_group(config.ssl_key_file)
        key_dir = os.path.dirname(config.ssl_key_file)
        if not os.path.exists(key_dir):
            if not config.handle_files_dirs:
                msg = _("Unable to write SSL key: No such file or directory: {key_dir}")
                msg = msg.format(key_dir=key_dir)
                raise OTPmeException(msg)
            try:
                filetools.create_dir(path=key_dir,
                                    user=config.user,
                                    group=config.group,
                                    mode=0o775)
            except IOError as e:
                if e.errno != e.errno.EACCES:
                    raise
        try:
            filetools.create_file(path=config.ssl_key_file,
                                    content=host_key,
                                    user=file_owner,
                                    group=file_group,
                                    mode=file_mode)
        except IOError as e:
            if e.errno != e.errno.EACCES:
                raise

    # Create CA file if it does not exist.
    if ca_data:
        file_owner, file_group, file_mode = get_file_owner_group(config.ssl_ca_file)
        ca_data_dir = os.path.dirname(config.ssl_ca_file)
        if not os.path.exists(ca_data_dir):
            if not config.handle_files_dirs:
                msg = _("Unable to write SSL CA data: No such file or directory: {ca_data_dir}")
                msg = msg.format(ca_data_dir=ca_data_dir)
                raise OTPmeException(msg)
            try:
                filetools.create_dir(path=ca_data_dir,
                                    user=config.user,
                                    group=config.group,
                                    mode=0o775)
            except IOError as e:
                if e.errno != e.errno.EACCES:
                    raise
        try:
            filetools.create_file(path=config.ssl_ca_file,
                                        content=ca_data,
                                        user=file_owner,
                                        group=file_group,
                                        mode=file_mode)
        except IOError as e:
            if e.errno != e.errno.EACCES:
                raise

    # Create site cert file if it does not exist.
    if site_cert:
        file_owner, file_group, file_mode = get_file_owner_group(config.ssl_site_cert_file)
        site_cert_dir = os.path.dirname(config.ssl_site_cert_file)
        if not os.path.exists(site_cert_dir):
            if not config.handle_files_dirs:
                msg = _("Unable to write site certficate: No such file or directory: {site_cert_dir}")
                msg = msg.format(site_cert_dir=site_cert_dir)
                raise OTPmeException(msg)
            try:
                filetools.create_dir(path=site_cert_dir,
                                    user=config.user,
                                    group=config.group,
                                    mode=0o775)
            except IOError as e:
                if e.errno != e.errno.EACCES:
                    raise
        try:
            filetools.create_file(path=config.ssl_site_cert_file,
                                        content=site_cert,
                                        user=file_owner,
                                        group=file_group,
                                        mode=file_mode)
        except IOError as e:
            if e.errno != e.errno.EACCES:
                raise

    # Create host key file if it does not exist.
    if host_auth_key:
        file_owner, file_group, file_mode = get_file_owner_group(config.host_key_file)
        key_dir = os.path.dirname(config.host_key_file)
        if not os.path.exists(key_dir):
            if not config.handle_files_dirs:
                msg = _("Unable to write host key: No such file or directory: {key_dir}")
                msg = msg.format(key_dir=key_dir)
                raise OTPmeException(msg)
            try:
                filetools.create_dir(path=key_dir,
                                    user=config.user,
                                    group=config.group,
                                    mode=0o775)
            except IOError as e:
                if e.errno != e.errno.EACCES:
                    raise
        try:
            filetools.create_file(path=config.host_key_file,
                                    content=host_auth_key,
                                    user="root",
                                    group="root",
                                    mode=file_mode)
        except IOError as e:
            if e.errno != e.errno.EACCES:
                raise

    # NOTE: this is now done on realm init and host/node join.
    ## Make sure files have sane permissions.
    #set_ssl_file_perms()


def load_data(ignore_missing=False):
    """
    Update data of our host "host_data" dictionary as well
    as SSL cert/key files.
    """
    f_host_cert = None
    f_host_key = None
    f_ca_data = None
    f_site_cert = None
    f_host_auth_key = None

    if not config.realm_init:
        if not os.path.exists(config.uuid_file):
            raise Exception(_("Host is not a realm member."))

    # Try to read hosts SSL cert from file.
    if os.access(config.ssl_cert_file, os.R_OK):
        try:
            fd = open(config.ssl_cert_file, "r")
            f_host_cert = fd.read()
            fd.close()
        except IOError as e:
            if e.errno != e.errno.EACCES:
                raise

    # Try to read hosts SSL cert key from file.
    if os.access(config.ssl_key_file, os.R_OK):
        try:
            fd = open(config.ssl_key_file, "r")
            f_host_key = fd.read()
            fd.close()
        except IOError as e:
            if e.errno != e.errno.EACCES:
                raise

    # Try to read hosts CA data from file,
    if os.access(config.ssl_ca_file, os.R_OK):
        try:
            fd = open(config.ssl_ca_file, "r")
            f_ca_data = fd.read()
            fd.close()
        except IOError as e:
            if e.errno != e.errno.EACCES:
                raise

    # Try to read site cert from file,
    if os.access(config.ssl_site_cert_file, os.R_OK):
        try:
            fd = open(config.ssl_site_cert_file, "r")
            f_site_cert = fd.read()
            fd.close()
        except IOError as e:
            if e.errno != e.errno.EACCES:
                raise

    # Try to read hosts RSA private key from file.
    if os.access(config.host_key_file, os.R_OK):
        try:
            fd = open(config.host_key_file, "r")
            f_host_auth_key = fd.read()
            fd.close()
        except IOError as e:
            if e.errno != e.errno.EACCES:
                raise
    try:
        host_fqdn = socket.getfqdn()
    except Exception as e:
        msg = _("Unable to get host FQDN: {e}")
        msg = msg.format(e=e)
        raise OTPmeException(msg)
    try:
        host_name = socket.gethostname()
    except Exception as e:
        msg = _("Unable to get host FQDN: {e}")
        msg = msg.format(e=e)
        raise OTPmeException(msg)

    if not ignore_missing:
        if not f_host_cert:
            msg = _("Unable to get host certificate from file: {ssl_cert_file}")
            msg = msg.format(ssl_cert_file=config.ssl_cert_file)
            raise OTPmeException(msg)
        if not f_host_key:
            msg = _("Unable to get host key from file: {ssl_key_file}")
            msg = msg.format(ssl_key_file=config.ssl_key_file)
            raise OTPmeException(msg)
        if not f_ca_data:
            msg = _("Unable to get host CA data from file: {ssl_ca_file}")
            msg = msg.format(ssl_ca_file=config.ssl_ca_file)
            raise OTPmeException(msg)
        if not f_site_cert:
            msg = _("Unable to get site certificate from file: {ssl_site_cert_file}")
            msg = msg.format(ssl_site_cert_file=config.ssl_site_cert_file)
            raise OTPmeException(msg)

    try:
        multiprocessing.host_data['type']
    except:
        multiprocessing.host_data['type'] = None
    try:
        multiprocessing.host_data['realm']
    except:
        multiprocessing.host_data['realm'] = None
    try:
        multiprocessing.host_data['site']
    except:
        multiprocessing.host_data['site'] = None
    multiprocessing.host_data['name'] = host_name
    multiprocessing.host_data['fqdn'] = host_fqdn
    multiprocessing.host_data['site_cert'] = f_site_cert
    multiprocessing.host_data['cert'] = f_host_cert
    multiprocessing.host_data['key'] = f_host_key
    multiprocessing.host_data['ca_data'] = f_ca_data
    multiprocessing.host_data['auth_key'] = f_host_auth_key
    return

def update_data(host_cert=None, host_key=None, ca_data=None,
    site_cert=None, host_auth_key=None, ignore_missing=None):
    """
    Update data of our host "host_data" dictionary as well
    as SSL cert/key files.
    """
    if not config.realm_init:
        if not os.path.exists(config.uuid_file):
            raise Exception(_("Host is not a realm member."))

    if ignore_missing is None:
        ignore_missing = False
        if config.realm_init:
            ignore_missing = True
        if config.realm_join:
            ignore_missing = True

    # Load host data.
    load_data(ignore_missing)
    # Get data from files.
    f_host_cert = multiprocessing.host_data['cert']
    f_host_key = multiprocessing.host_data['key']
    f_host_auth_key = multiprocessing.host_data['auth_key']
    f_ca_data = multiprocessing.host_data['ca_data']
    f_site_cert = multiprocessing.host_data['site_cert']

    # Update SSL cert file if needed.
    if host_cert is None:
        host_cert = f_host_cert

    # Update SSL key file if needed.
    if host_key is None:
        host_key = f_host_key

    # Update RSA key file if needed.
    if host_auth_key is None:
        host_auth_key = f_host_auth_key

    update_files = False
    if host_key:
        if host_key != f_host_key:
            update_files = True
    else:
        host_key = f_host_key

    if host_cert:
        if host_cert != f_host_cert:
            update_files = True
    else:
        host_cert = f_host_cert

    if ca_data:
        if ca_data != f_ca_data:
            update_files = True
    else:
        ca_data = f_ca_data

    if site_cert:
        if site_cert != f_site_cert:
            update_files = True
    else:
        site_cert = f_site_cert

    if host_auth_key:
        if host_auth_key != f_host_auth_key:
            update_files = True
    else:
        host_auth_key = f_host_auth_key

    if update_files:
        update_files = False
        update_ssl_files(host_cert=host_cert,
                        host_key=host_key,
                        ca_data=ca_data,
                        site_cert=site_cert,
                        host_auth_key=host_auth_key)

    if config.realm_init:
        multiprocessing.host_data['type'] = None
        return

    # Try to get host object from backend.
    myhost = None
    if config.uuid:
        for t in ['node', 'host']:
            myhost = backend.get_object(object_type=t, uuid=config.uuid)
            if myhost:
                break

    if not myhost:
        msg = _("Don't know who i am :(. Please make sure {uuid_file} points to the correct OTPme object UUID.")
        msg = msg.format(uuid_file=config.uuid_file)
        raise OTPmeException(msg)
    if not myhost.name:
        msg = _("Uuhh don't know my hostname. This is most likely a result of a broken object configuration.")
        raise OTPmeException(msg)
    if not myhost.type:
        msg = _("Uuhh, '{hostname}' does not have host type set. This is most likely a result of a broken object configuration.")
        msg = msg.format(hostname=myhost.name)
        raise OTPmeException(msg)

    if host_cert != myhost.cert:
        update_ssl_files(host_cert=host_cert)

    if not host_cert:
        host_cert = myhost.cert
        update_ssl_files(host_cert=host_cert)

    myrealm = backend.get_object(object_type="realm", name=myhost.realm)
    if not myrealm:
        msg = _("Realm '{realm}' does does not exists.")
        msg = msg.format(realm=myhost.realm)
        raise OTPmeException(msg)

    # Update SSL CA file if needed.
    if myrealm.ca_data:
        if myrealm.ca_data != ca_data:
            ca_data = myrealm.ca_data
            update_ssl_files(ca_data=ca_data)

    # Update site SSL cert file if needed.
    mysite = backend.get_object(object_type="site", uuid=myhost.site_uuid)
    if not mysite:
        msg = _("Unknown site: {site_uuid}")
        msg = msg.format(site_uuid=myhost.site_uuid)
        raise Exception(msg)
    if mysite.cert:
        if mysite.cert != site_cert:
            site_cert = mysite.cert
            update_ssl_files(site_cert=site_cert)

    # NOTE: this is now done on realm init and host/node join.
    ## Make sure files have sane permissions.
    #set_ssl_file_perms()

    host_name = myhost.name
    host_fqdn = myhost.fqdn
    host_type = myhost.type
    host_realm = myhost.realm
    host_site = myhost.site

    config.host_type = host_type

    # Update host data in config.
    multiprocessing.host_data['name'] = host_name
    multiprocessing.host_data['fqdn'] = host_fqdn
    multiprocessing.host_data['type'] = host_type
    multiprocessing.host_data['realm'] = host_realm
    multiprocessing.host_data['site'] = host_site
    multiprocessing.host_data['site_cert'] = site_cert
    multiprocessing.host_data['cert'] = host_cert
    multiprocessing.host_data['key'] = host_key
    multiprocessing.host_data['ca_data'] = ca_data
    multiprocessing.host_data['auth_key'] = host_auth_key
