# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
#
# Version: $Id$
"""This module defines the code for handling domains."""

import datetime
import os

from gnuviechadmin.exceptions import ValidationFailedError
from gnuviechadmin.backend.settings import config, get_template, \
    get_template_dir, get_template_string
from gnuviechadmin.backend.BackendTo import Record, Domain
from gnuviechadmin.backend.BackendEntity import BackendEntity
from gnuviechadmin.backend.BackendEntityHandler import BackendEntityHandler


class DomainEntity(BackendEntity):
    """Entity class for DNS domains."""

    # the valid domain types
    _valid_domain_types = ("MASTER", "SLAVE")

    def __init__(self, delegate, verbose = False, **kwargs):
        """Initializes the DomainEntity instance.

        `delegate` is the corresponding database object.
        If `verbose` is `True` verbose logging is turned on.
        """
        BackendEntity.__init__(self, delegate, verbose)
        self.ns1 = None
        self.ns2 = None
        self.mxrr = None
        self.ipaddr = None
        for (key, value) in kwargs.items():
            self.__setattr__(key, value)
        if not self.delegateto.type:
            self.delegateto.type = self.getdefaultdomaintype()
        if not self.ns1:
            self.ns1 = config.get('domain', 'defaultns1')
        if not self.ns2:
            self.ns2 = config.get('domain', 'defaultns2')
        if not self.mxrr:
            self.mxrr = config.get('domain', 'defaultmx')
        if not self.ipaddr:
            self.ipaddr = config.get('domain', 'defaultip')
        self.delegateto.type = self.delegateto.type.upper()
        self.validate()

    def getdefaultdomaintype(self):
        """Returns the default domain type."""
        return self._valid_domain_types[0]

    def validate(self):
        """Validates the consistency if the entity instance and
        dependent entities."""
        BackendEntity.validate(self)
        if not self.delegateto.type in self._valid_domain_types:
            raise ValidationFailedError(
                self, "invalid domain type %s" % (self.delegateto.type))
        if self.delegateto.type == 'SLAVE' and not self.delegateto.master:
            raise ValidationFailedError(
                self, "you have to specify a master for slave domains.")
        if self.delegateto.type == 'MASTER':
            if not self.ns1 or not self.ns2:
                raise ValidationFailedError(
                    self, "two nameservers must be specified.")
            if not self.mxrr:
                raise ValidationFailedError(
                    self, "a primary mx host must be specified.")

    def _getnewserial(self, oldserial = None):
        """Gets a new zone serial number for the DNS domain entity."""
        current = datetime.datetime.now()
        datepart = "%04d%02d%02d" % \
                   (current.year, current.month, current.day)
        retval = None
        if oldserial:
            if str(oldserial)[:len(datepart)] == datepart:
                retval = oldserial + 1
        if not retval:
            retval = int("%s01" % (datepart))
        return retval

    def _getnewsoa(self):
        """Gets a new SOA record for the DNS domain entity."""
        return '%s %s %d %d %d %d %d' % \
               (self.ns1,
                config.get('domain', 'defaulthostmaster'),
                self._getnewserial(),
                config.getint('domain', 'defaultrefresh'),
                config.getint('domain', 'defaultretry'),
                config.getint('domain', 'defaultexpire'),
                config.getint('domain', 'defaultminimumttl'))

    def update_serial(self, session):
        """Updates the serial of the domain."""
        query = session.query(Record)
        soarecord = query.get_by(Record.c.type == 'SOA',
                                 Record.c.domainid == self.delegateto.domainid)
        parts = soarecord.content.split(" ")
        parts[2] = str(self._getnewserial(int(parts[2])))
        soarecord.content = " ".join(parts)
        session.save(soarecord)
        session.flush()

    def _get_vhost_dir(self):
        """Gets the directory name for the Apache VirtualHost of the
        domain."""
        return os.path.join(self.delegateto.sysuser.home,
                            self.delegateto.name,
                            config.get('domain', 'htdir'))

    def _get_log_dir(self):
        """Gets the Apache log file directory for the domain."""
        return os.path.join(config.get('domain', 'logpath'),
                            self.delegateto.name)

    def _get_stats_dir(self):
        """Gets the statistics dir for the domain."""
        return os.path.join(config.get('domain', 'statspath'),
                            self.delegateto.name)

    def _create_vhost_dir(self):
        """Creates the Apache VirtualHost directory for the domain."""
        vhostdir = self._get_vhost_dir()
        self.logger.debug("creating virtual host dir %s" % (vhostdir))
        cmd = 'mkdir -p "%s"' % (vhostdir)
        self.sucommand(cmd)
        for tpl in [tpl for tpl in os.listdir(
            get_template_dir(config.get('domain', 'htdocstemplate'))) \
                    if not tpl.startswith('.')]:
            self.logger.debug("processing template %s" % (tpl))
            template = get_template(config.get('domain', 'htdocstemplate'),
                                    tpl)
            template = template.substitute({
                'domain': self.delegateto.name})
            self.write_to_file(os.path.join(vhostdir, tpl), template)
        cmd = 'chown -R %(username)s:%(group)s "%(dir)s"' % {
            'username': self.delegateto.sysuser.username,
            'group': config.get('sysuser', 'defaultgroup'),
            'dir': vhostdir}
        self.sucommand(cmd)

    def _create_log_dir(self):
        """Creates the Apache log file directory for the domain."""
        cmd = 'mkdir -p "%s"' % (self._get_log_dir())
        self.sucommand(cmd)

    def _get_auth_userfile(self):
        """Gets the file name of the password file for statistic
        logins for the domain."""
        authdir = config.get('domain', 'authdir')
        if not os.path.isdir(authdir):
            cmd = 'mkdir -p "%s"' % (authdir)
            self.sucommand(cmd)
        return os.path.join(authdir, '%s.passwd' % (self.delegateto.name))

    def _create_stats_dir(self):
        """Creates the statistics directory for the domain and sets
        Apache .htaccess password protection."""
        statsdir = self._get_stats_dir()
        authfile = self._get_auth_userfile()
        cmd = 'htpasswd -m -c -b "%s" "%s" "%s"' % (
            authfile,
            self.delegateto.sysuser.username,
            self.delegateto.sysuser.clearpass)
        self.sucommand(cmd)
        cmd = 'mkdir -p "%s"' % (statsdir)
        self.sucommand(cmd)
        template = get_template(config.get('domain', 'conftemplates'),
                                config.get('domain', 'statshtaccesstemplate'))
        template = template.substitute({
            'domain': self.delegateto.name,
            'userfile': authfile})
        self.write_to_file(os.path.join(self._get_stats_dir(),
                                        '.htaccess'), template)

    def _create_stats_conf(self):
        """Creates the modlogan statistics configuration for the
        domain."""
        modlogandir = os.path.join(config.get('domain',
                                              'modlogandir'),
                                   self.delegateto.sysuser.username)
        cmd = 'mkdir -p "%s"' % (modlogandir)
        self.sucommand(cmd)
        template = get_template(config.get('domain', 'conftemplates'),
                                config.get('domain', 'modlogantemplate'))
        template = template.substitute({
            'statsdir': self._get_stats_dir(),
            'logdir': self._get_log_dir(),
            'domain': self.delegateto.name,
            'domainesc': self.delegateto.name.replace('.', '\.')})
        self.write_to_file(os.path.join(modlogandir,
                                        self.delegateto.name + '.conf'),
                           template)

    def _create_apache_conf(self):
        """Creates the Apache configuration file for the domain."""
        template = get_template(config.get('domain', 'conftemplates'),
                                config.get('domain', 'apachetemplate'))
        template = template.substitute({
            'ipaddr': self.ipaddr,
            'statsdir': self._get_stats_dir(),
            'logdir': self._get_log_dir(),
            'domain': self.delegateto.name,
            'docroot': self._get_vhost_dir()})
        self.write_to_file(os.path.join(config.get('domain', 'sitesdir'),
                                        self.delegateto.name), template)

    def _mail_domain(self):
        """Mail a summary of the domain data."""
        template = get_template(config.get('common', 'mailtemplates'),
                                config.get('domain', 'create.mail'))
        text = template.substitute({
            'sysuser': self.delegateto.sysuser.username,
            'domain': self.delegateto.name,
            'docroot': self._get_vhost_dir(),
            'statspass': self.delegateto.sysuser.clearpass})
        template = get_template_string(config.get('domain', 'create_subject'))
        subject = template.substitute({
            'domain': self.delegateto.name})
        self.send_mail(subject, text)

    def create_hook(self, session):
        """Hook for the creation of the domain.

        This method is called by
        `gnuviechadmin.backend.BackendEntityHandler.create()`.
        """
        self.delegateto.records.append(Record(
            name = self.delegateto.name, type = 'SOA',
            content = self._getnewsoa(),
            ttl = config.getint('domain', 'defaultttl')))
        self.delegateto.records.append(Record(
            name = self.delegateto.name, type = 'NS', content = self.ns1,
            ttl = config.getint('domain', 'defaultttl')))
        self.delegateto.records.append(Record(
            name = self.delegateto.name, type = 'NS', content = self.ns2,
            ttl = config.getint('domain', 'defaultttl')))
        self.delegateto.records.append(Record(
            name = self.delegateto.name, type = 'MX', content = self.mxrr,
            ttl = config.getint('domain', 'defaultttl'),
            prio = config.getint('domain', 'defaultmxprio')))
        self.delegateto.records.append(Record(
            name = self.delegateto.name, type = 'A', content = self.ipaddr,
            ttl = config.getint('domain', 'defaultttl')))
        self.delegateto.records.append(Record(
            name = "www.%s" % (self.delegateto.name), type = 'A',
            content = self.ipaddr,
            ttl = config.getint('domain', 'defaultttl')))
        session.save_or_update(self.delegateto)
        session.flush()
        self._create_vhost_dir()
        self._create_log_dir()
        self._create_stats_dir()
        self._create_stats_conf()
        self._create_apache_conf()
        self._mail_domain()

    def _delete_apache_conf(self):
        """Deletes the Apache configuration file for the domain."""
        cmd = 'a2dissite %s' % (self.delegateto.name)
        self.sucommand(cmd)
        cmd = 'rm "%s"' % (os.path.join(config.get('domain', 'sitesdir'),
                                        self.delegateto.name))
        self.sucommand(cmd)

    def _delete_stats_conf(self):
        """Deletes the modlogan stastics configuration for the
        domain."""
        cmd = 'rm "%s"' % (os.path.join(config.get('domain', 'modlogandir'),
                                        self.delegateto.sysuser.username,
                                        self.delegateto.name + '.conf'))
        self.sucommand(cmd)

    def _archive_stats_dir(self):
        """Archives the statistics directory for the domain."""
        archive = os.path.join(self.delegateto.sysuser.home,
                               '%(domain)s-stats.tar.gz' % {
            'domain': self.delegateto.name})
        cmd = 'tar czf "%(archive)s" --directory="%(statsbase)s" ' + \
            '"%(statsdir)s"' % {
            'archive': archive,
            'statsbase': config.get('domain', 'statspath'),
            'statsdir': self.delegateto.name}
        self.sucommand(cmd)
        cmd = 'rm -r "%(statsdir)s"' % {
            'statsdir': self._get_stats_dir()}
        self.sucommand(cmd)
        cmd = 'chown "%(username)s:%(group)s" "%(archive)s"' % {
            'username': self.delegateto.sysuser.username,
            'group': config.get('sysuser', 'defaultgroup'),
            'archive': archive}
        self.sucommand(cmd)

    def _archive_log_dir(self):
        """Archives the Apache log file directory for the domain."""
        archive = os.path.join(self.delegateto.sysuser.home,
                               '%(domain)s-logs.tar.gz' % {
            'domain': self.delegateto.name})
        cmd = 'tar czf "%(archive)s" --directory="%(logbase)s" ' + \
            '"%(logdir)s"' % {
            'archive': archive,
            'logbase': config.get('domain', 'logpath'),
            'logdir': self.delegateto.name}
        self.sucommand(cmd)
        cmd = 'rm -r "%(logdir)s"' % {
            'logdir': self._get_log_dir()}
        self.sucommand(cmd)
        cmd = 'chown "%(username)s:%(group)s" "%(archive)s"' % {
            'username': self.delegateto.sysuser.username,
            'group': config.get('sysuser', 'defaultgroup'),
            'archive': archive}
        self.sucommand(cmd)

    def _archive_vhost_dir(self):
        """Archives the Apache VirtualHost directory for the domain."""
        archive = os.path.join(self.delegateto.sysuser.home,
                               '%(domain)s-vhost.tar.gz' % {
            'domain': self.delegateto.name})
        cmd = 'tar czf "%(archive)s" --directory="%(vhostbase)s" ' + \
            '"%(vhostdir)s"' % {
            'archive': archive,
            'vhostbase': self.delegateto.sysuser.home,
            'vhostdir': self.delegateto.name}
        self.sucommand(cmd)
        cmd = 'rm -r "%(vhostdir)s"' % {
            'vhostdir': os.path.join(self.delegateto.sysuser.home,
                                      self.delegateto.name)}
        self.sucommand(cmd)
        cmd = 'chown "%(username)s:%(group)s" "%(archive)s"' % {
            'username': self.delegateto.sysuser.username,
            'group': config.get('sysuser', 'defaultgroup'),
            'archive': archive}
        self.sucommand(cmd)

    def delete_hook(self, session):
        """Deletes domain related files and directories.

        This method is called by `BackendEntityHandler.delete()`.
        """
        self._delete_apache_conf()
        self._delete_stats_conf()
        self._archive_stats_dir()
        self._archive_log_dir()
        self._archive_vhost_dir()


class DomainHandler(BackendEntityHandler):
    """BackendEntityHandler for Domain entities."""

    def __init__(self, verbose = False):
        """Initialize the DomainHandler."""
        BackendEntityHandler.__init__(self, DomainEntity, Domain, verbose)