#!/opt/panel-migrator/thirdparties/python/bin/python


import re
import yaml
import getopt
import sys
import time
import os
import pymysql

# plesk dump restore will convert any value bigger to the scientific format 1E+power instead of numeric
plesk_bad_limit = 90000000


class Color(object):
    default = '\033[0m'
    white = '\033[97m'
    red = '\033[91m'
    yellow = '\033[93m'
    green = '\033[92m'


def write_yaml(filename, data):
    log("Writing dump file")
    with open(filename, 'w') as f:
        yaml.safe_dump(data, f, encoding='utf-8', allow_unicode=True)
    log("File '%s' is ready" % filename, Color.green)


def sql_to_dict(query, connection):
    with connection.cursor(pymysql.cursors.DictCursor) as cursor:
        cursor.execute(query)
        result = cursor.fetchall()
    return result


def clear_log(warnings, level):
    if len(warnings) > 0 and warnings[-1]["level"] >= level:
        while len(warnings) > 0 and warnings[-1]["level"] >= level and warnings[-1]["color"] == Color.default:
            warnings.pop()


def log(text, color=Color.default, display=True, data=None):
    level = 0
    split = '. '
    verbosity = 1
    if data:
        level = data["level"]
        clear_log(data["warnings"], level)
        data["warnings"].append({"level": level, "text": split * level + color + text + Color.default, "color": color})
        verbosity = data.get("verbosity")
    if display and verbosity:
        now = time.strftime("[%Y-%m-%d %H:%M:%S]")
        print(Color.white + now, Color.default + split * level + color + text + Color.default)


def show_warnings(warnings):
    log("Printing warnings")
    clear_log(warnings, 0)
    for warning in warnings:
        print(warning["text"])


def filter_list(somelist):
    return list(
        value
        for value
        in somelist
        if value is not None and len(value) > 0
    )


def filter_dict(**kwargs):
    return dict(
        (key, value)
        for key, value
        in kwargs.iteritems()
        if isinstance(value, bool) or value is not None and len(value) > 0
    )


def check_dir(path, data):
    if os.path.isdir(path):
        return path
    data["level"] += 1
    log("Directory '%s' is not found" % path, data=data, color=Color.yellow)
    data["level"] -= 1


def get_lines(filename, display=True, data=None, strip=True):
    if not os.path.isfile(filename):
        log("Cannot open file '%s'" % filename, Color.yellow, display=display, data=data)
        return []
    with open(filename) as f:
        lines = f.readlines()
        if strip:
            lines = [line.rstrip('\n') for line in lines]
        return lines


def get_passwd_file(filename, data):
    result = []
    for line in get_lines(filename, data=data):
        if ':' in line:
            result.append(line.split(':'))
    return result


def get_ispmgr_conf(data):
    conf = {
        "accounts": {},
        "paths": {},
        "domains": {},
        "MTA": None,
        "POP3": None,
        "database_servers": {},
        "databases": [],
        "presets": {}
    }
    lines = iter(get_lines(data["files"]["ispmgr"], data=data))
    for line in lines:
        if line.startswith('Account ') and '{' in line:
            accountname = line.split()[1].strip('"')
            account = {accountname: {}}
            line = next(lines, None)
            while not line.startswith('}'):
                account[accountname].update({line.split()[0]: line.split()[1]})
                line = next(lines, None)
            conf["accounts"].update(account)
        elif line.startswith('Preset ') and '{' in line:
            presetname = line.split()[1].strip('"')
            preset = {presetname: {}}
            line = next(lines, None)
            while not line.startswith('}'):
                preset[presetname].update({line.split()[0]: line.split()[1]})
                line = next(lines, None)
            conf["presets"].update(preset)
        elif line.startswith('path '):
            path_line = line.split(' ', 1)[1]
            splitted_path_line = path_line.split(' ', 1)
            conf["paths"].update({splitted_path_line[0].strip(): splitted_path_line[1].strip()})
        elif line.startswith('Domain '):
            sline = line.split()
            conf["domains"].update({sline[1].strip(','): sline[-1]})
        elif line.startswith('MTA '):
            conf["MTA"] = line.split()[1]
        elif line.startswith('POP3 '):
            conf["POP3"] = line.split()[1].strip()
        elif line.startswith('DbServer ') and '{' in line:
            dbservername = line.split()[1].strip('"')
            dbserver = {dbservername: {}}
            line = next(lines, None)
            while not line.startswith('}'):
                dbserver[dbservername].update({line.split()[0]: line.split()[1]})
                line = next(lines, None)
            conf["database_servers"].update(dbserver)
            log("Database server \"%s\" was detected. Don't forget to describe it in config file" % dbservername,
                Color.yellow, data=data)
        elif line.startswith('DbAssign '):
            sline = line.split()
            conf["databases"].append({"name": sline[2], "server": sline[1].strip('"'), "userid": sline[3]})
    return conf


def get_httpd_conf(data):
    if "httpd.conf" not in data["loaded"]["ispmgr"]["paths"]:
        return
    log("Getting httpd configuration")
    conf = {
        "virtualhosts": []
    }
    lines = iter(get_lines(data["loaded"]["ispmgr"]["paths"]["httpd.conf"], data=data))
    for line in lines:
        if line.startswith('<VirtualHost ') and '>' in line:
            virtualhost = {}
            # gets ip address from string like <VirtualHost 185.98.6.188:8080 >
            ip = re.findall(r'[0-9]+(?:\.[0-9]+){3}', line)
            if len(ip) > 0:
                virtualhost["ip"] = ip[0]
            line = next(lines, None)
            while not line.startswith('</VirtualHost>'):
                stripped_line = line.strip()
                for param in ['ServerName', 'AssignUserID', 'DocumentRoot', 'ServerAlias', 'Redirect']:
                    if stripped_line.startswith(param):
                        virtualhost.update({param: stripped_line.split()[1:]})
                line = next(lines, None)
            conf["virtualhosts"].append(virtualhost)
    return conf


def get_exim_domains(data):
    post_domains = {}
    if "exim-virtualdomains" in data["loaded"]["ispmgr"]["paths"]:
        log("Getting exim domains file")
        for line in get_passwd_file(data["loaded"]["ispmgr"]["paths"]["exim-virtualdomains"], data):
            if not line[0] in post_domains.keys():
                post_domains.update({line[0]: line[1:]})
    return post_domains


def get_dovecot_mailboxes(data):
    if "dovecot.passwd" in data["loaded"]["ispmgr"]["paths"]:
        log("Getting dovecot passwd file")
        return get_passwd_file(data["loaded"]["ispmgr"]["paths"]["dovecot.passwd"], data)
    return []


def get_exim_passwd(data):
    if "exim-passwd" in data["loaded"]["ispmgr"]["paths"]:
        log("Getting exim passwd file")
        return get_passwd_file(data["loaded"]["ispmgr"]["paths"]["exim-passwd"], data)
    return []


def get_exim_aliases(data):
    if "aliases" in data["loaded"]["ispmgr"]["paths"]:
        log("Getting exim aliases file")
        return get_passwd_file(data["loaded"]["ispmgr"]["paths"]["aliases"], data)
    return []


def get_exim_vaclist(data):
    if "exim-vaclist" in data["loaded"]["ispmgr"]["paths"]:
        log("Getting exim vaclist file")
        return get_lines(data["loaded"]["ispmgr"]["paths"]["exim-vaclist"], data)
    return []


def get_panel_conf(data):
    log("Getting panel configuration")
    data["files"].update(
        passwd="/etc/passwd",
        shadow="/etc/shadow",
        ispmgr="/usr/local/ispmgr/etc/ispmgr.conf",
        hostname="/etc/hostname",
        cron="/var/spool/cron"
    )
    data["loaded"].update(
        passwd=get_passwd_file(data["files"]["passwd"], data),
        shadow=get_passwd_file(data["files"]["shadow"], data),
        ispmgr=get_ispmgr_conf(data),
        hostname=get_lines(data["files"]["hostname"], data)
    )
    data["loaded"].update(
        httpd=get_httpd_conf(data),
        database_users=mark_database_users(dump_database_users(data)),
        exim_domains=get_exim_domains(data),
        dovecot_mailboxes=get_dovecot_mailboxes(data),
        exim_passwd=get_exim_passwd(data),
        exim_aliases=get_exim_aliases(data),
        exim_vaclist=get_exim_vaclist(data)
    )


def setup_connections(data):
    for database in data["loaded"]["ispmgr"]["database_servers"].iteritems():
        user = None
        passwd = None
        socket = None
        if database[1].get("Type") == "mysql":
            user = database[1].get("User")
            passwd = database[1].get("Password")
            socket = database[1].get("SockPath")
        if user and passwd and socket:
            log("Connecting to database server \"%s\"" % database[0])
            try:
                conn = pymysql.connect(user=user, passwd=passwd, unix_socket=socket)
                data["connections"].append({"name": database[0], "type": database[1].get("Type"), "connection": conn})
            except pymysql.err.OperationalError as e:
                print("Failed to connect to database server \"{db}\": {error}".format(db=database[0], error=e.args[1]))


def close_connections(data):
    for connection in data["connections"]:
        log("Closing connection to database server \"%s\"" % connection["name"])
        connection["connection"].close()


def get_user_password(data, user):
    for elem in data["loaded"]["shadow"]:
        if elem[0] == user:
            return elem[1].lstrip("*")
    return


def get_ispmgr_resellers(data):
    ispmgr_resellers = []
    ispmgr_customers = get_ispmgr_users(data["loaded"], 'root')
    passwd_resellers = [user[0] for user in data["loaded"]["passwd"] if user[4] == "Reseller"]
    for ispmgr_customer in ispmgr_customers:
        if ispmgr_customer in passwd_resellers:
            ispmgr_resellers.append(ispmgr_customer)
    return ispmgr_resellers


def get_ispmgr_customers(data, owner):
    ispmgr_users = get_ispmgr_users(data["loaded"], owner)
    if owner == "root":
        passwd_resellers = [user[0] for user in data["loaded"]["passwd"] if user[4] == "Reseller"]
        return [ispmgr_user for ispmgr_user in ispmgr_users if ispmgr_user not in passwd_resellers]
    return ispmgr_users


def get_ispmgr_users(data_loaded, owner):
    ispmgr_customers = []
    for account_name, account_data in data_loaded["ispmgr"]["accounts"].iteritems():
        for account_param, account_value in account_data.iteritems():
            if "Owner" in account_param and account_value == owner:
                ispmgr_customers.append(account_name)
    return ispmgr_customers


def get_user_limits(data, user, reseller=None):
    limits = {}
    source = data["loaded"]["ispmgr"]["accounts"][user]
    limits.update(filter_dict(
        disk_space=get_preset_param(source, "DiskLimit"),
        max_cl=get_preset_param(source, "UserLimit") if reseller else None,
        max_subftp_users=get_preset_param(source, "FtpLimit"),
        max_box=get_preset_param(source, "MailLimit"),
        max_site=get_preset_param(source, "WebDomainLimit"),
        max_db=get_preset_param(source, "DatabaseLimit"),
        max_traffic=get_preset_param(source, "BandwidthLimit"),
        mbox_quota="-1"
    ))
    if "disk_space" in limits:
        # to avoid convertation to scientific format in PMM
        if int(limits["disk_space"]) > int(plesk_bad_limit):
            log("Setting user {user} limits disk_space to unlimited".format(user=user), color=Color.yellow)
            limits.update(disk_space="-1")
        else:
            limits.update(disk_space=str(int(limits["disk_space"])*1000000))
    if "max_traffic" in limits:
        # to avoid convertation to scientific format in PMM
        if int(limits["max_traffic"]) > int(plesk_bad_limit):
            log("Setting user {user} limits max_traffic to unlimited".format(user=user), color=Color.yellow)
            limits.update(max_traffic="-1")
        else:
            limits.update(max_traffic=str(int(limits["max_traffic"])*1000000))
    return limits


def get_preset_param(source, limit):
    return source[limit] if limit in source else None


def get_shell(data, user):
    if get_preset_param(data["loaded"]["ispmgr"]["accounts"][user], "Shell") == "On":
        return "/bin/bash"
    else:
        return "/bin/false"


def get_user_web_files(data, owner):
    web_files = []
    docroot = check_dir(os.path.join(data["loaded"]["ispmgr"]["paths"]["DefaultHomeDir"], owner, "data"), data)
    # logdir = check_dir(os.path.join(data["loaded"]["ispmgr"]["paths"]["DefaultHomeDir"], owner, "data", "logs"), data)
    if docroot:
        web_files.append(filter_dict(
            source=docroot,
            target="{webspace_root}",
            exclude=["/email", "/tmp", "/mod-tmp", "/logs"]
        ))
    # if logdir:
    #     web_files.append(filter_dict(
    #         source=logdir,
    #         target="{webspace_root}/ispmgr_logs"
    #     ))
    return filter_list(web_files)


def get_ispmgr_user_by_name(data, user):
    for item in data["loaded"]["passwd"]:
        if item[0] == user:
            return item


def get_domains_from_ispmgr_config(data, owner):
    uid = get_ispmgr_user_by_name(data, owner)[2]
    return [domain for domain, ownerid in data["loaded"]["ispmgr"]["domains"].iteritems() if ownerid == uid]


def get_ispmgr_www_domain_names(data, owner):
    domains = []
    for domain in data["loaded"]["httpd"]["virtualhosts"]:
        if domain.get("AssignUserID")[0] == owner:
            domains.append(domain["ServerName"][0].lstrip("www."))
    for domain in data["loaded"]["httpd"]["virtualhosts"]:
        if domain.get("AssignUserID")[0] == owner:
            for alias in domain.get("ServerAlias", []):
                if "*" not in alias and alias.lstrip("www.") not in domains:
                    domains.append(alias.lstrip("www."))
    return domains


def get_ispmgr_www_domain_ips(data, owner):
    ips = []
    for domain in data["loaded"]["httpd"]["virtualhosts"]:
        if domain.get("AssignUserID")[0] == owner:
            if domain.get("ip") is not None:
                ips.append({"type": "shared", "address": domain.get("ip")})
    # delete duplicates from list of dicts
    return [dict(t) for t in set([tuple(d.items()) for d in ips])]


def prepare_domains_list(data):
    log("Preparing domains list")
    domains = []
    for domain in data["loaded"]["httpd"]["virtualhosts"]:
        domains.append(domain["ServerName"][0].lstrip("www."))
    for domain in data["loaded"]["httpd"]["virtualhosts"]:
        for alias in domain.get("ServerAlias", []):
            if "*" not in alias and alias.lstrip("www.") not in domains:
                domains.append(alias.lstrip("www."))
    for domain in data["loaded"]["ispmgr"]["domains"].iteritems():
        if domain not in domains:
            domains.append(domain)
    return domains


def get_target_document_root(data, owner, domain):
    for host in data["loaded"]["httpd"]["virtualhosts"]:
        if domain == host.get("ServerName", [])[0] or domain in host.get("ServerAlias", []):
            path_len = len(os.path.join(data["loaded"]["ispmgr"]["paths"]["DefaultHomeDir"], owner, "data"))
            return host.get("DocumentRoot")[0][path_len:].lstrip('/')


def get_sys_user(data, user):
    sys_user = {}
    password = get_user_password(data, user)
    sys_user.update(filter_dict(
        login=user,
        password=password,
        password_type='hash' if password else None,
        shell=get_shell(data, user)
    ))
    return sys_user


def get_ispmgr_databases(data, owner):
    databases = []
    gid = get_ispmgr_user_by_name(data, owner)[3]
    for database in data["loaded"]["ispmgr"]["databases"]:
        if database["userid"] == gid:
            databases.append(database)
    return databases


def mark_database_users(users):
    user_names = [user["User"] for user in users]
    for user in users:
        if user_names.count(user["User"]) == 1:
            user.update(type="single")
        else:
            user.update(type="multi")
    return users


def dump_database_users(data):
    database_users = []
    setup_connections(data)
    for connection in data["connections"]:
        if connection["type"] != "mysql":
            continue
        log("Dumping database users from database server \"%s\"" % connection["name"])
        connection_database_users = (sql_to_dict(
            '''SELECT DISTINCT db.User, db.Db, user.Password
            FROM mysql.db
            LEFT JOIN mysql.user
            ON db.User = user.User;''', connection["connection"]
        ))
        for connection_database_user in connection_database_users:
            connection_database_user.update(
                Db=connection_database_user["Db"].replace("\_", "_"),
                server=connection["name"]
            )
        database_users.extend(connection_database_users)
    close_connections(data)
    return database_users


def get_ispmgr_database_users(data, databases):
    db_users = []
    for database in databases:
        for db_user in data["loaded"]["database_users"]:
            if db_user["Db"] == database["name"] and db_user["User"] not in [user["User"] for user in db_users]:
                db_users.append(db_user)
    return db_users


def get_ispmgr_mail_users(data, domain):
    mail_users = []
    if data["loaded"]["ispmgr"]["POP3"] == "dovecot":
        for dovecot_mail_user in data["loaded"]["dovecot_mailboxes"]:
            dovecot_mail_user_splitted = dovecot_mail_user[0].split('@')
            if not dovecot_mail_user[0].startswith('#') and dovecot_mail_user_splitted[1] == domain:
                mail_user = {}
                mail_user.update(
                    fullname=dovecot_mail_user[0],
                    name=dovecot_mail_user_splitted[0],
                    domain=dovecot_mail_user_splitted[1],
                    password=dovecot_mail_user[1],
                    maildir=dovecot_mail_user[-1]
                )
                mail_users.append(mail_user)
    if data["loaded"]["ispmgr"]["MTA"] == "exim":
        for exim_mail_user in data["loaded"]["exim_passwd"]:
            for mail_user in mail_users:
                if exim_mail_user[0] == mail_user["fullname"]:
                    mail_user.update(limit=str(int(exim_mail_user[3])*1000000) if exim_mail_user[3] != '0' else '-1')
    return mail_users


def parse_dns_zone_lines(lines, domain):
    domain_idna = domain.decode('idna')
    dns_zone = {"ttl": None, "soa": None, "records": []}
    for line in lines:
        if line.startswith("$TTL"):
            dns_zone.update(ttl=line.split()[1])
            continue
        src, ttl, rtype, dst = line.split(None, 3)
        src = src.decode('utf-8').decode('idna')
        dst = dst.decode('utf-8').decode('idna')
        if src == "@":
            src = domain_idna + "."
        elif not src.endswith("."):
            src += "." + domain_idna + "."
        if rtype == "SOA":
            ssline = dst.split(None, 2)
            dns_zone.update(soa={"email": ssline[1], "params": ssline[2].strip("()").split()})
        elif rtype == "NS":
            dns_zone["records"].append({"type": "NS", "src": src, "dst": dst})
        elif rtype == "A":
            dns_zone["records"].append({"type": "A", "src": src, "dst": dst})
        elif rtype == "MX":
            ssline = dst.split()
            dns_zone["records"].append({"type": "MX", "src": src, "dst": ssline[1], "opt": ssline[0]})
        elif rtype == "TXT":
            dns_zone["records"].append({"type": "TXT", "src": src, "dst": dst.strip('"')})
        elif rtype == "CNAME":
            dns_zone["records"].append({"type": "CNAME", "src": src, "dst": dst})
    return dns_zone


def filter_dns_records(data, all_records):
    records = []
    for record in all_records:
        if record["type"] == "A" and record["src"].lstrip("www.").rstrip(".") in data["prepared"]["domains"]:
            continue
        records.append(record)
    return records


def get_forwarding(data, domain):
    for host in data["loaded"]["httpd"]["virtualhosts"]:
        if domain == host.get("ServerName", [])[0]:
            httpd_redirect = host.get("Redirect")
            if httpd_redirect and httpd_redirect[1] == '/':
                return {"redirect_type": "moved-temporarily" if httpd_redirect[0] == "temp" else "moved-permanently",
                        "redirect_url": httpd_redirect[2]}
    return {"redirect_type": None, "redirect_url": None}


def get_ispmgr_cron_tasks(data, owner):
    cron_tasks = []
    cron_lines = get_lines(os.path.join(data["files"]["cron"], owner), display=False)
    mail = None
    for line in cron_lines:
        cron_task = dict(
            mail=None,
            period=None,
            minute=None,
            hour=None,
            day_of_week=None,
            day_of_month=None,
            month=None,
            command=None
        )
        if line.startswith("MAILTO="):
            cron_mail = line.split("=")[1].strip('"')
            if len(cron_mail) > 0:
                mail = cron_mail
        elif len(line) == 0 or line.startswith("PATH="):
            continue
        elif line.startswith("@"):
            sline = line.split(None, 1)
            cron_task.update(
                mail=mail,
                period=sline[0].lstrip("@"),
                command=sline[-1]
            )
        else:
            sline = line.split(None, 5)
            cron_task.update(
                mail=mail,
                perion="cron_style",
                minute=sline[0],
                hour=sline[1],
                day_of_week=sline[2],
                day_of_month=sline[3],
                month=sline[4],
                command=sline[-1]
            )
        if cron_task["command"]:
            cron_tasks.append(cron_task)
    return cron_tasks


def get_resellers(data):
    data["level"] += 1
    resellers = []
    for ispmgr_reseller in get_ispmgr_resellers(data):
        log("Reseller %s" % ispmgr_reseller, data=data)
        reseller = {}
        password = get_user_password(data, ispmgr_reseller)
        reseller.update(filter_dict(
            login=ispmgr_reseller,
            password=password,
            password_type='hash' if password else None,
            customers=get_customers(data, ispmgr_reseller),
            limits=get_user_limits(data, ispmgr_reseller, reseller=True)
        ))
        resellers.append(reseller)
    data["level"] -= 1
    return resellers


def get_customers(data, owner):
    data["level"] += 1
    customers = []
    for ispmgr_customer in get_ispmgr_customers(data, owner):
        log("Customer %s" % ispmgr_customer, data=data)
        customer = {}
        password = get_user_password(data, ispmgr_customer)
        customer.update(filter_dict(
            login=ispmgr_customer,
            password=password,
            password_type='hash' if password else None,
            subscriptions=get_subscriptions(data, ispmgr_customer)
        ))
        customers.append(customer)
    data["level"] -= 1
    return customers


def get_subscriptions(data, owner):
    data["level"] += 1
    subscriptions = []
    name = owner+".webspace"
    log("Subscription %s" % name, data=data)
    subscription = {}
    subscription.update(filter_dict(
        name=name,
        ip_addresses=get_ispmgr_www_domain_ips(data, owner),
        sys_user=get_sys_user(data, owner),
        limits=get_user_limits(data, owner),
        web_files=get_user_web_files(data, owner),
        addon_domains=get_addon_domains(data, owner),
        databases=get_databases(data, owner),
        database_users=get_database_users(data, owner, utype="multi"),
        ftp_users=get_ftp_users(data, owner),
        scheduled_tasks=get_scheduled_tasks(data, owner),
        mail_service={"disabled_by": ["client"]}
    ))
    subscriptions.append(subscription)
    data["level"] -= 1
    return subscriptions


def get_scheduled_tasks(data, owner):
    scheduled_tasks = []
    for ispmgr_cron_task in get_ispmgr_cron_tasks(data, owner):
        scheduled_task = {}
        scheduled_task.update(filter_dict(
            type="exec",
            command=ispmgr_cron_task["command"],
            period=ispmgr_cron_task["period"],
            minute=ispmgr_cron_task["minute"],
            hour=ispmgr_cron_task["hour"],
            day_of_week=ispmgr_cron_task["day_of_week"],
            day_of_month=ispmgr_cron_task["day_of_month"],
            month=ispmgr_cron_task["month"],
            is_active=False,
            # description=,
            # notify=,
            notifications_email=ispmgr_cron_task["mail"]
        ))
        scheduled_tasks.append(scheduled_task)
    return scheduled_tasks


def get_addon_domains(data, owner):
    data["level"] += 1
    addon_domains = []
    httpd_domains = get_ispmgr_www_domain_names(data, owner)
    ispmgr_conf_domains = get_domains_from_ispmgr_config(data, owner)
    ispmgr_domains = list(set(httpd_domains + ispmgr_conf_domains))
    for ispmgr_domain in ispmgr_domains:
        ispmgr_domain_idna = ispmgr_domain.decode('idna')
        log("addon domain %s" % ispmgr_domain_idna, data=data)
        addon_domain = {}
        docroot = get_target_document_root(data, owner, ispmgr_domain) if ispmgr_domain in httpd_domains else None
        forwarding = get_forwarding(data, ispmgr_domain)
        addon_domain.update(filter_dict(
            name=ispmgr_domain_idna,
            target_document_root=docroot if not forwarding["redirect_url"] else None,
            no_web_hosting="true" if ispmgr_domain not in httpd_domains else None,
            mail_service=get_mail_service(data, ispmgr_domain),
            dns_zone=get_dns_zone(data, ispmgr_domain),
            forwarding_url=forwarding["redirect_url"],
            forwarding_type=forwarding["redirect_type"]
        ))
        addon_domains.append(addon_domain)
    data["level"] -= 1
    return addon_domains


def get_dns_zone(data, ispmgr_domain):
    if not data["loaded"]["ispmgr"]["paths"].get("DomainZonesPath"):
        return
    dns_zone_file_path = os.path.join(data["loaded"]["ispmgr"]["paths"]["DomainZonesPath"], ispmgr_domain)
    dns_zone_lines = get_lines(dns_zone_file_path, display=False)
    dns_zone = {}
    if not dns_zone_lines:
        dns_zone.update(type="master", disabled_by=["client"])
        return dns_zone
    data["level"] += 1
    ispmgr_dns_zone = parse_dns_zone_lines(dns_zone_lines, ispmgr_domain)
    log("dns zone %s" % ispmgr_domain, data=data)
    dns_zone.update(filter_dict(
        type="master",
        ttl=ispmgr_dns_zone["ttl"],
        refresh=ispmgr_dns_zone["soa"]["params"][1],
        retry=ispmgr_dns_zone["soa"]["params"][2],
        expire=ispmgr_dns_zone["soa"]["params"][3],
        minimum=ispmgr_dns_zone["soa"]["params"][4],
        email=ispmgr_dns_zone["soa"]["email"],
        dns_records=filter_dns_records(data, ispmgr_dns_zone["records"])
    ))
    data["level"] -= 1
    return dns_zone


def get_mail_service(data, domain):
    mail_service = {}
    if data["loaded"]["ispmgr"]["MTA"] == "exim":
        if domain in data["loaded"]["exim_domains"].keys():
            mail_service.update(filter_dict(
                mail_users=get_mail_users(data, domain),
                catch_all_action=get_exim_catch_all_action(data, domain)
            ))
        else:
            mail_service.update(disabled_by=["client"])
    return mail_service


def get_exim_catch_all_action(data, domain):
    if data["loaded"]["exim_domains"][domain][1] == '':
        return {"name": "reject"}
    elif data["loaded"]["exim_domains"][domain][1] == "/dev/null":
        return {"name": "bounce"}
    else:
        return {"name": "forward", "forward_email": data["loaded"]["exim_domains"][domain][1]}


def get_mail_users(data, domain):
    data["level"] += 1
    mail_users = []
    for ispmgr_mail_user in get_ispmgr_mail_users(data, domain):
        log("mail user {name}@{dom}".format(name=ispmgr_mail_user["name"], dom=ispmgr_mail_user["domain"]), data=data)
        mail_user = {}
        maildir = check_dir(ispmgr_mail_user["maildir"], data)
        mail_user.update(filter_dict(
            name=ispmgr_mail_user["name"],
            password=ispmgr_mail_user["password"],
            password_type="hash",
            directory=maildir,
            disk_quota=ispmgr_mail_user["limit"],
            forwarding=get_forwardings(data, ispmgr_mail_user["fullname"]),
            auto_reply=get_auto_reply(data, ispmgr_mail_user["fullname"], maildir)
        ))
        mail_users.append(mail_user)
    mail_users.extend(get_mail_aliases(data, domain))
    data["level"] -= 1
    return mail_users


def get_auto_reply(data, name, path):
    if not path:
        return
    vacation_filename = os.path.join(os.path.dirname(path), "vacation", "message.txt")
    ispmgr_vacation = get_lines(vacation_filename, display=False, strip=False)
    if len(ispmgr_vacation) == 0:
        return
    data["level"] += 1
    log("auto reply", data=data)
    auto_reply = {}
    auto_reply.update(filter_dict(
        subject=ispmgr_vacation[1].split(":")[1].strip(),
        message=''.join(ispmgr_vacation[4:]),
        enabled=True if name in data["loaded"]["exim_vaclist"] else False,
    ))
    data["level"] -= 1
    return auto_reply


def get_mail_aliases(data, domain):
    mail_users = []
    if data["loaded"]["ispmgr"]["MTA"] == "exim":
        for alias_string in data["loaded"]["exim_aliases"]:
            if len(alias_string) < 3 or alias_string[2] != "fwd" or alias_string[0].split("@")[1] != domain or '@':
                continue
            mail_user = {}
            mail_user.update(filter_dict(
                name=alias_string[0].split("@")[0],
                mailbox=False,
                forwarding=filter_dict(addresses=verify_forwardings(alias_string[1].split(","), alias_string[0]))
            ))
            mail_users.append(mail_user)
    return mail_users


def get_forwardings(data, name):
    addresses = []
    if data["loaded"]["ispmgr"]["MTA"] == "exim":
        for alias in data["loaded"]["exim_aliases"]:
            if alias[0] == name:
                addresses.extend(verify_forwardings(alias[1].split(","), name))
    return filter_dict(addresses=addresses)


def verify_forwardings(forwarding_addresses, name):
    addresses = []
    for forwarding in forwarding_addresses:
        if '@' in forwarding and forwarding != name:
            addresses.append(forwarding)
    return addresses


def get_databases(data, owner):
    data["level"] += 1
    databases = []
    for ispmgr_database in get_ispmgr_databases(data, owner):
        log("database %s" % ispmgr_database["name"], data=data)
        database = {}
        database.update(filter_dict(
            name=ispmgr_database["name"],
            server=ispmgr_database["server"],
            type=data["loaded"]["ispmgr"]["database_servers"][ispmgr_database["server"]]["Type"],
            users=get_database_users(data, owner, utype="single", db=ispmgr_database)
        ))
        databases.append(database)
    data["level"] -= 1
    return databases


def get_database_users(data, owner, utype=None, db=None):
    data["level"] += 1
    database_users = []
    databases_list = [db] if db else get_ispmgr_databases(data, owner)
    for ispmgr_database_user in get_ispmgr_database_users(data, databases_list):
        if utype and ispmgr_database_user["type"] != utype:
            continue
        log("database {t}user {u}".format(t=utype + ' ' if utype else '', u=ispmgr_database_user["User"]), data=data)
        database_user = {}
        database_user.update(filter_dict(
            login=ispmgr_database_user["User"],
            password=ispmgr_database_user["Password"],
            password_type="hash",
            server=ispmgr_database_user["server"]
        ))
        database_users.append(database_user)
    data["level"] -= 1
    return database_users


def get_ftp_users(data, owner):
    data["level"] += 1
    ftp_users = []
    uid = get_ispmgr_user_by_name(data, owner)[2]
    for item in data["loaded"]["passwd"]:
        if item[2] != uid or item[0] == owner:
            continue
        log("ftp user %s" % item[0], data=data)
        # Check if home directory of ftp user is located under vhost directory
        # as said by dmedvedev@plesk, we can use relative path to home directory of ftp users
        docroot = check_dir(os.path.join(data["loaded"]["ispmgr"]["paths"]["DefaultHomeDir"], owner, "data"), data)
        if docroot:
            # if ftp user home dir is located under vhost dir of subscription
            if docroot in item[5]:
                homedir = item[5][len(docroot)+1:]
            # if ftp user home dir is located somewhere else
            else:
                log(u"Ftp user '{s}' has homedir outside of vhost dir, skipping".format(s=item[0]), Color.yellow)
                continue
        else:
            # if can not find vhost dir, leave ftp user home dir unchanged
            homedir = item[5]
        password = get_user_password(data, item[0])
        ftp_user = {}
        ftp_user.update(
            login=item[0],
            password=password,
            password_type='hash' if password else None,
            home_directory=homedir
        )
        ftp_users.append(ftp_user)
    data["level"] -= 1
    return ftp_users


def get_hosting_plans(data):
    data["level"] += 1
    hosting_plans = []
    for preset_name, values in data["loaded"]["ispmgr"]["presets"].iteritems():
        log("Hosting plan %s" % preset_name, data=data)
        hosting_plan = {}
        hosting_plan.update(
            name=preset_name,
            items=get_hosting_plan_items(values)
        )
        hosting_plans.append(hosting_plan)
    data["level"] -= 1
    return hosting_plans


def get_hosting_plan_items(values):
    items = {}
    items.update(filter_dict(
        disk_space=get_preset_param(values, "Disk"),
        max_subftp_users=get_preset_param(values, "Ftp"),
        max_box=get_preset_param(values, "Mail"),
        max_dom=get_preset_param(values, "Domain"),
        max_db=get_preset_param(values, "Database"),
        max_traffic=get_preset_param(values, "Bandwidth"),
    ))
    if "disk_space" in items:
        # to avoid convertation to scientific format in PMM
        if int(items["disk_space"]) > int(plesk_bad_limit):
            log("Setting hosting plan disk_space to unlimited", color=Color.yellow)
            items.update(disk_space="-1")
        else:
            items.update(disk_space=str(int(items["disk_space"])*1000000))
    if "max_traffic" in items:
        # to avoid convertation to scientific format in PMM
        if int(items["max_traffic"]) > int(plesk_bad_limit):
            log("Setting hosting plan max_traffic to unlimited", color=Color.yellow)
            items.update(max_traffic="-1")
        else:
            items.update(max_traffic=str(int(items["max_traffic"])*1000000))
    return items


def get_dump(args):
    dump = {}
    data = {}
    data.update(
        files={},
        warnings=[],
        loaded={},
        level=-1,
        connections=[],
        verbosity=args.get("verbosity"),
        prepared={}
    )
    get_panel_conf(data)
    data["prepared"].update(domains=prepare_domains_list(data))
    log("Dumping objects")
    log("START OBJECTS MAPPING", color=Color.white) if data.get("verbosity") else None
    dump.update(filter_dict(
        # reseller_plans=get_reseller_plans()
        hosting_plans=get_hosting_plans(data),
        resellers=get_resellers(data),
        customers=get_customers(data, 'root')
    ))
    log("FINISH OBJECTS MAPPING", color=Color.white) if data.get("verbosity") else None
    show_warnings(data["warnings"])
    return dump


def print_usage():
    print(
        '''Usage: ispmgr4-hosting-description.py [OPTIONS]...
    -f, --file          output filename
    -v                  verbosity
Example:
    ispmgr4-hosting-description.py -f myserver.yaml'''
    )


def get_args():
    config = {
        "file": "hosting-description.yaml",
        "verbosity": None
    }
    try:
        opts, args = getopt.getopt(sys.argv[1:], "hvf:", ["help", "file="])
    except getopt.GetoptError:
        print_usage()
        sys.exit(1)
    for opt, arg in opts:
        if opt in ("-h", '--help'):
            print_usage()
            sys.exit()
        elif opt == "-v":
            config["verbosity"] = 1
        elif opt in ("-f", "--file"):
            config["file"] = arg
    return config


if __name__ == "__main__":
    arguments = get_args()
    write_yaml(arguments["file"], get_dump(arguments))
