""" This module contains models related to domain names. """ from __future__ import absolute_import, unicode_literals from django.db import models, transaction from django.conf import settings from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext as _ from model_utils.models import TimeStampedModel from model_utils import Choices DNS_DOMAIN_TYPES = Choices( ('MASTER', _('Master')), ('SLAVE', _('Slave')), ('NATIVE', _('Native')), ) # see https://doc.powerdns.com/md/authoritative/domainmetadata/ DNS_DOMAIN_METADATA_KINDS = Choices( 'ALLOW-DNSUPDATE-FROM', 'ALSO-NOTIFY', 'AXFR-MASTER-TSIG', 'AXFR-SOURCE', 'FORWARD-DNSUPDATE', 'GSS-ACCEPTOR-PRINCIPAL', 'GSS-ALLOW-AXFR-PRINCIPAL', 'LUA-AXFR-SCRIPT', 'NSEC3NARROW', 'NSEC3PARAM', 'PRESIGNED', 'PUBLISH_CDNSKEY', 'PUBLISH_CDS', 'SOA-EDIT', 'SOA-EDIT-DNSUPDATE', 'TSIG-ALLOW-AXFR', 'TSIG-ALLOW-DNSUPDATE', ) DNS_TSIG_KEY_ALGORITHMS = Choices( ('hmac-md5', _('HMAC MD5')), ('hmac-sha1', _('HMAC SHA1')), ('hmac-sha224', _('HMAC SHA224')), ('hmac-sha256', _('HMAC SHA256')), ('hmac-sha384', _('HMAC SHA384')), ('hmac-sha512', _('HMAC SHA512')), ) class DomainBase(TimeStampedModel): """ This is the base model for domains. """ domain = models.CharField(_('domain name'), max_length=255, unique=True) customer = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_('customer'), blank=True, null=True, on_delete=models.CASCADE) class Meta: abstract = True @python_2_unicode_compatible class MailDomain(DomainBase): """ This is the model for mail domains. Mail domains are used to configure the mail servers (SMTP/IMAP/POP3). Mail addresses are assigned to these mail domains. """ class Meta: verbose_name = _('Mail domain') verbose_name_plural = _('Mail domains') def __str__(self): return self.domain def get_mailaddresses(self): """ Get a list of mail addresses assigned to this mail domain. """ return self.mailaddress_set.all() mailaddresses = property(get_mailaddresses) class HostingDomainManager(models.Manager): """ Default Manager for :py:class:`HostingDomain`. """ @transaction.atomic def create_for_hosting_package( self, hosting_package, domain, commit, **kwargs ): from hostingpackages.models import CustomerHostingPackageDomain hostingdomain = self.create( customer=hosting_package.customer, domain=domain, **kwargs) hostingdomain.maildomain = MailDomain.objects.create( customer=hosting_package.customer, domain=domain) custdomain = CustomerHostingPackageDomain.objects.create( hosting_package=hosting_package, domain=hostingdomain) if commit: hostingdomain.save() custdomain.save() return hostingdomain @python_2_unicode_compatible class HostingDomain(DomainBase): """ This is the model for hosting domains. A hosting domain is linked to a customer hosting account. """ maildomain = models.OneToOneField( MailDomain, verbose_name=_('mail domain'), blank=True, null=True, help_text=_('assigned mail domain for this domain'), on_delete=models.CASCADE, ) objects = HostingDomainManager() class Meta: verbose_name = _('Hosting domain') verbose_name_plural = _('Hosting domains') def __str__(self): return self.domain @python_2_unicode_compatible class DNSDomain(DomainBase): """ This model represents a DNS zone. The model is similar to the domain table in the PowerDNS schema specified in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. .. code-block:: sql CREATE TABLE domains ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, master VARCHAR(128) DEFAULT NULL, last_check INT DEFAULT NULL, type VARCHAR(6) NOT NULL, notified_serial INT DEFAULT NULL, account VARCHAR(40) DEFAULT NULL, CONSTRAINT c_lowercase_name CHECK ( ((name)::TEXT = LOWER((name)::TEXT))) ); CREATE UNIQUE INDEX name_index ON domains(name); """ # name is represented by domain master = models.CharField(max_length=128, blank=True, null=True) last_check = models.IntegerField(null=True) domaintype = models.CharField( max_length=6, choices=DNS_DOMAIN_TYPES, db_column='type') notified_serial = models.IntegerField(null=True) # account is represented by customer_id # check constraint is added via RunSQL in migration class Meta: verbose_name = _('DNS domain') verbose_name_plural = _('DNS domains') def __str__(self): return self.domain @python_2_unicode_compatible class DNSRecord(models.Model): """ This model represents a DNS record. The model is similar to the record table in the PowerDNS schema specified in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. .. code-block:: sql CREATE TABLE records ( id SERIAL PRIMARY KEY, domain_id INT DEFAULT NULL, name VARCHAR(255) DEFAULT NULL, type VARCHAR(10) DEFAULT NULL, content VARCHAR(65535) DEFAULT NULL, ttl INT DEFAULT NULL, prio INT DEFAULT NULL, change_date INT DEFAULT NULL, disabled BOOL DEFAULT 'f', ordername VARCHAR(255), auth BOOL DEFAULT 't', CONSTRAINT domain_exists FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE, CONSTRAINT c_lowercase_name CHECK ( ((name)::TEXT = LOWER((name)::TEXT))) ); CREATE INDEX rec_name_index ON records(name); CREATE INDEX nametype_index ON records(name,type); CREATE INDEX domain_id ON records(domain_id); CREATE INDEX recordorder ON records ( domain_id, ordername text_pattern_ops); """ domain = models.ForeignKey('DNSDomain', on_delete=models.CASCADE) name = models.CharField( max_length=255, blank=True, null=True, db_index=True) recordtype = models.CharField( max_length=10, blank=True, null=True, db_column='type') content = models.CharField(max_length=65535, blank=True, null=True) ttl = models.IntegerField(null=True) prio = models.IntegerField(null=True) change_date = models.IntegerField(null=True) disabled = models.BooleanField(default=False) ordername = models.CharField(max_length=255) auth = models.BooleanField(default=True) # check constraint and index recordorder are added via RunSQL in migration class Meta: verbose_name = _('DNS record') verbose_name_plural = _('DNS records') index_together = [ ['name', 'recordtype'] ] def __str__(self): return "{name} IN {type} {content}".format( name=self.name, type=self.recordtype, content=self.content) @python_2_unicode_compatible class DNSSupermaster(models.Model): """ This model represents the supermasters table in the PowerDNS schema specified in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. .. code-block:: sql CREATE TABLE supermasters ( ip INET NOT NULL, nameserver VARCHAR(255) NOT NULL, account VARCHAR(40) NOT NULL, PRIMARY KEY(ip, nameserver) ); """ ip = models.GenericIPAddressField() nameserver = models.CharField(max_length=255) # account is replaced by customer customer = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_('customer'), on_delete=models.CASCADE) class Meta: verbose_name = _('DNS supermaster') verbose_name_plural = _('DNS supermasters') unique_together = ( ('ip', 'nameserver') ) def __str__(self): return "{ip} {nameserver}".format( ip=self.ip, nameserver=self.nameserver) @python_2_unicode_compatible class DNSComment(models.Model): """ This model represents the comments table in the PowerDNS schema specified in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. The comments table is used to store user comments related to individual DNS records. .. code-block:: sql CREATE TABLE comments ( id SERIAL PRIMARY KEY, domain_id INT NOT NULL, name VARCHAR(255) NOT NULL, type VARCHAR(10) NOT NULL, modified_at INT NOT NULL, account VARCHAR(40) DEFAULT NULL, comment VARCHAR(65535) NOT NULL, CONSTRAINT domain_exists FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE, CONSTRAINT c_lowercase_name CHECK ( ((name)::TEXT = LOWER((name)::TEXT))) ); CREATE INDEX comments_domain_id_idx ON comments (domain_id); CREATE INDEX comments_name_type_idx ON comments (name, type); CREATE INDEX comments_order_idx ON comments (domain_id, modified_at); """ domain = models.ForeignKey('DNSDomain', on_delete=models.CASCADE) name = models.CharField(max_length=255) commenttype = models.CharField(max_length=10, db_column='type') modified_at = models.IntegerField() # account is replaced by customer customer = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_('customer'), on_delete=models.CASCADE) comment = models.CharField(max_length=65535) # check constraint is added via RunSQL in migration class Meta: verbose_name = _('DNS comment') verbose_name_plural = _('DNS comments') index_together = [ ['name', 'commenttype'], ['domain', 'modified_at'] ] def __str__(self): return "{name} IN {type}: {comment}".format( name=self.name, type=self.commenttype, comment=self.comment) @python_2_unicode_compatible class DNSDomainMetadata(models.Model): """ This model represents the domainmetadata table in the PowerDNS schema specified in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. The domainmetadata table is used to store domain meta data as described in https://doc.powerdns.com/md/authoritative/domainmetadata/. .. code-block:: sql CREATE TABLE domainmetadata ( id SERIAL PRIMARY KEY, domain_id INT REFERENCES domains(id) ON DELETE CASCADE, kind VARCHAR(32), content TEXT ); CREATE INDEX domainidmetaindex ON domainmetadata(domain_id); """ domain = models.ForeignKey('DNSDomain', on_delete=models.CASCADE) kind = models.CharField(max_length=32, choices=DNS_DOMAIN_METADATA_KINDS) content = models.TextField() class Meta: verbose_name = _('DNS domain metadata item') verbose_name_plural = _('DNS domain metadata items') def __str__(self): return "{domain} {kind} {content}".format( domain=self.domain.domain, kind=self.kind, content=self.content) @python_2_unicode_compatible class DNSCryptoKey(models.Model): """ This model represents the cryptokeys table in the PowerDNS schema specified in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. .. code-block:: sql CREATE TABLE cryptokeys ( id SERIAL PRIMARY KEY, domain_id INT REFERENCES domains(id) ON DELETE CASCADE, flags INT NOT NULL, active BOOL, content TEXT ); CREATE INDEX domainidindex ON cryptokeys(domain_id); """ domain = models.ForeignKey('DNSDomain', on_delete=models.CASCADE) flags = models.IntegerField() active = models.BooleanField(default=True) content = models.TextField() class Meta: verbose_name = _('DNS crypto key') verbose_name_plural = _('DNS crypto keys') def __str__(self): return "{domain} {content}".format( domain=self.domain.domain, content=self.content) @python_2_unicode_compatible class DNSTSIGKey(models.Model): """ This model represents the tsigkeys table in the PowerDNS schema specified in https://doc.powerdns.com/md/authoritative/backend-generic-mypgsql/. .. code-block:: sql CREATE TABLE tsigkeys ( id SERIAL PRIMARY KEY, name VARCHAR(255), algorithm VARCHAR(50), secret VARCHAR(255), CONSTRAINT c_lowercase_name CHECK ( ((name)::TEXT = LOWER((name)::TEXT))) ); CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm); """ name = models.CharField(max_length=255) algorithm = models.CharField( max_length=50, choices=DNS_TSIG_KEY_ALGORITHMS) secret = models.CharField(max_length=255) # check constraint is added via RunSQL in migration class Meta: verbose_name = _('DNS TSIG key') verbose_name_plural = _('DNS TSIG keys') unique_together = [ ['name', 'algorithm'] ] def __str__(self): return "{name} {algorithm} XXXX".format( name=self.name, algorithm=self.algorithm)