""" This module contains models related to domain names. """ from __future__ import absolute_import from django.conf import settings from django.db import models, transaction from django.utils.translation import gettext as _ from model_utils import Choices from model_utils.models import TimeStampedModel 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 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(DomainBase.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 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 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 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 ) 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) 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 ) 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 ) 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 ) 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 )