Browse Source

Merge progress from production system

Sebastian Lohff 5 months ago
parent
commit
a3b6209e0a

+ 106
- 106
api/dnshelper.py View File

@@ -11,121 +11,121 @@ import dns.resolver
11 11
 # FIXME: DNS timeouts
12 12
 
13 13
 def compareRecords(rrset, expected):
14
-	result = {
15
-		"nameMissing": [],
16
-		"rrMissing": [],
17
-		"rrExtra": [],
18
-	}
14
+    result = {
15
+        "nameMissing": [],
16
+        "rrMissing": [],
17
+        "rrExtra": [],
18
+    }
19 19
 
20
-	for domain, rrtype, content in expected:
21
-		for rrrec in rrset:
22
-			if domain == rrrec.name.to_text() and dns.rdatatype.from_text(rrtype) == rrrec.rdtype:
23
-				for name in content:
24
-					if name not in map(lambda _x: _x.to_text(), rrrec.items):
25
-						# record missing
26
-						result["rrMissing"].append((domain, rrtype, name))
20
+    for domain, rrtype, content in expected:
21
+        for rrrec in rrset:
22
+            if domain == rrrec.name.to_text() and dns.rdatatype.from_text(rrtype) == rrrec.rdtype:
23
+                for name in content:
24
+                    if name not in map(lambda _x: _x.to_text(), rrrec.items):
25
+                        # record missing
26
+                        result["rrMissing"].append((domain, rrtype, name))
27 27
 
28
-				for item in rrrec.items:
29
-					if item.to_text() not in content:
30
-						# superfluous record
31
-						result["rrExtra"].append((domain, rrtype, item.to_text()))
28
+                for item in rrrec.items:
29
+                    if item.to_text() not in content:
30
+                        # superfluous record
31
+                        result["rrExtra"].append((domain, rrtype, item.to_text()))
32 32
 
33
-				break
34
-		else:
35
-			# domain +  rr nicht in nameserver
36
-			result["nameMissing"].append((domain, rrtype))
33
+                break
34
+        else:
35
+            # domain +  rr nicht in nameserver
36
+            result["nameMissing"].append((domain, rrtype))
37 37
 
38
-	success = not any(len(_x) > 0 for _x in result.values())
38
+    success = not any(len(_x) > 0 for _x in result.values())
39 39
 
40
-	return success, result
40
+    return success, result
41 41
 
42 42
 
43 43
 def dnsQuery(domain, rrType, nameserverIp):
44
-	dname = dns.name.from_text(domain)
45
-	req = dns.message.make_query(dname, dns.rdatatype.from_text(rrType))
46
-	resp = dns.query.udp(req, nameserverIp, timeout=2.0)
44
+    dname = dns.name.from_text(domain)
45
+    req = dns.message.make_query(dname, dns.rdatatype.from_text(rrType))
46
+    resp = dns.query.udp(req, nameserverIp, timeout=2.0)
47 47
 
48
-	if resp.rcode() != dns.rcode.NXDOMAIN:
49
-		rrset = resp.answer + resp.authority + resp.additional
50
-		return True, rrset
51
-	else:
52
-		return False, []
48
+    if resp.rcode() != dns.rcode.NXDOMAIN:
49
+        rrset = resp.answer + resp.authority + resp.additional
50
+        return True, rrset
51
+    else:
52
+        return False, []
53 53
 
54 54
 
55 55
 def checkDomain(domain, tldNameserver, nameservers):
56
-	result = []
57
-
58
-	if nameservers.count() == 0:
59
-		return [("err", "Domain %s has no nameservers attached to it, nothing to check" % domain)]
60
-
61
-	# build record set
62
-	nsRecords = [(domain, "NS", list(ns.name for ns in nameservers))]
63
-	glueRecords = []
64
-	for ns in nameservers:
65
-		if ns.name.endswith("." + domain):
66
-			if ns.glueIPv4 or ns.glueIPv6:
67
-				if ns.glueIPv4:
68
-					glueRecords.append((ns.name, "A", [ns.glueIPv4]))
69
-				if ns.glueIPv6:
70
-					glueRecords.append((ns.name, "AAAA", [ns.glueIPv6]))
71
-			else:
72
-				result.append(("err", "Nameserver %s is under domain %s, but has no glue entries." % (ns.name, domain)))
73
-
74
-	# 1. TLD nameserver
75
-	try:
76
-		found, rrset = dnsQuery(domain, "ANY", tldNameserver)
77
-		if found:
78
-			success, errors = compareRecords(rrset, nsRecords + glueRecords)
79
-			if success:
80
-				result.append(("succ", "All records present in TLD nameserver"))
81
-			else:
82
-				result.append(("err", "Record mismatch between TLD nameserver and WHOIS database", errors))
83
-		else:
84
-			result.append(("err", "Domain %s not found in TLD nameserver" % (domain,)))
85
-	except (dns.exception.Timeout, OSError):
86
-		result.append(("err", "TLD nameserver is currently not reachable"))
87
-
88
-	# find other records...
89
-
90
-	# 2. your nameservers
91
-	for ns in nameservers:
92
-		addr = None
93
-		if ns.glueIPv4:
94
-			addr = ns.glueIPv4
95
-		elif ns.glueIPv6:
96
-			addr = ns.glueIPv6
97
-		else:
98
-			for rrType in ("A", "AAAA"):
99
-				try:
100
-					r = dns.resolver.Resolver()
101
-					r.timeout = 2.0
102
-					q = r.query(ns.name, rdtype=dns.rdatatype.from_text(rrType))
103
-					addr = q.response.answer[0].items[0].address
104
-				except (dns.exception.DNSException, OSError):
105
-					pass
106
-
107
-		if addr:
108
-			err = False
109
-			errDict = {"nameMissing": [], "rrMissing": [], "rrExtra": []}
110
-			try:
111
-				for rec in (nsRecords + glueRecords):
112
-					found, rrset = dnsQuery(rec[0], rec[1], addr)
113
-
114
-					#success, errors = compareRecords(rrset, nsRecords + glueRecords)
115
-					success, errors = compareRecords(rrset, [rec])
116
-					if not success:
117
-						err = True
118
-						for k in errors.keys():
119
-							errDict[k].extend(errors[k])
120
-
121
-				if not err:
122
-					result.append(("succ", "Nameserver %s is configured correctly" % ns.name))
123
-				else:
124
-					result.append(("err", "Nameserver %s (via %s) recordset does not match the database" % (ns.name, addr), errDict))
125
-			except (dns.exception.DNSException, OSError):
126
-				result.append(("err", "Nameserver %s is not reachable (via %s)" % (ns.name, addr)))
127
-				
128
-		else:
129
-			result.append(("err", "Can't resolv an ip address for nameserver %s" % ns.name))
130
-
131
-	return result
56
+    result = []
57
+
58
+    if nameservers.count() == 0:
59
+        return [("err", "Domain %s has no nameservers attached to it, nothing to check" % domain)]
60
+
61
+    # build record set
62
+    nsRecords = [(domain, "NS", list(ns.name for ns in nameservers))]
63
+    glueRecords = []
64
+    for ns in nameservers:
65
+        if ns.name.endswith("." + domain):
66
+            if ns.glueIPv4 or ns.glueIPv6:
67
+                if ns.glueIPv4:
68
+                    glueRecords.append((ns.name, "A", [ns.glueIPv4]))
69
+                if ns.glueIPv6:
70
+                    glueRecords.append((ns.name, "AAAA", [ns.glueIPv6]))
71
+            else:
72
+                result.append(("err", "Nameserver %s is under domain %s, but has no glue entries." % (ns.name, domain)))
73
+
74
+    # 1. TLD nameserver
75
+    try:
76
+        found, rrset = dnsQuery(domain, "ANY", tldNameserver)
77
+        if found:
78
+            success, errors = compareRecords(rrset, nsRecords + glueRecords)
79
+            if success:
80
+                result.append(("succ", "All records present in TLD nameserver"))
81
+            else:
82
+                result.append(("err", "Record mismatch between TLD nameserver and WHOIS database", errors))
83
+        else:
84
+            result.append(("err", "Domain %s not found in TLD nameserver" % (domain,)))
85
+    except (dns.exception.Timeout, OSError):
86
+        result.append(("err", "TLD nameserver is currently not reachable"))
87
+
88
+    # find other records...
89
+
90
+    # 2. your nameservers
91
+    for ns in nameservers:
92
+        addr = None
93
+        if ns.glueIPv4:
94
+            addr = ns.glueIPv4
95
+        elif ns.glueIPv6:
96
+            addr = ns.glueIPv6
97
+        else:
98
+            for rrType in ("A", "AAAA"):
99
+                try:
100
+                    r = dns.resolver.Resolver()
101
+                    r.timeout = 2.0
102
+                    q = r.query(ns.name, rdtype=dns.rdatatype.from_text(rrType))
103
+                    addr = q.response.answer[0].items[0].address
104
+                except (dns.exception.DNSException, OSError):
105
+                    pass
106
+
107
+        if addr:
108
+            err = False
109
+            errDict = {"nameMissing": [], "rrMissing": [], "rrExtra": []}
110
+            try:
111
+                for rec in (nsRecords + glueRecords):
112
+                    found, rrset = dnsQuery(rec[0], rec[1], addr)
113
+
114
+                    #success, errors = compareRecords(rrset, nsRecords + glueRecords)
115
+                    success, errors = compareRecords(rrset, [rec])
116
+                    if not success:
117
+                        err = True
118
+                        for k in errors.keys():
119
+                            errDict[k].extend(errors[k])
120
+
121
+                if not err:
122
+                    result.append(("succ", "Nameserver %s is configured correctly" % ns.name))
123
+                else:
124
+                    result.append(("err", "Nameserver %s (via %s) recordset does not match the database" % (ns.name, addr), errDict))
125
+            except (dns.exception.DNSException, OSError):
126
+                result.append(("err", "Nameserver %s is not reachable (via %s)" % (ns.name, addr)))
127
+                
128
+        else:
129
+            result.append(("err", "Can't resolv an ip address for nameserver %s" % ns.name))
130
+
131
+    return result

+ 6
- 6
api/urls.py View File

@@ -7,13 +7,13 @@ from django.conf.urls import url
7 7
 from . import views as api_views
8 8
 
9 9
 urlpatterns = [
10
-	url(r'asblock/free-as/$', api_views.asblockFreeAS, name='asblock-free-as'),
11
-	url(r'asblock/free-subnet/$', api_views.freeSubnet, name='inetnum-free-subnet'),
12
-	url(r'inetnum/get-subnet/$', api_views.getSubnet, name='inetnum-get-subnet'),
10
+    url(r'asblock/free-as/$', api_views.asblockFreeAS, name='asblock-free-as'),
11
+    url(r'asblock/free-subnet/$', api_views.freeSubnet, name='inetnum-free-subnet'),
12
+    url(r'inetnum/get-subnet/$', api_views.getSubnet, name='inetnum-get-subnet'),
13 13
 
14
-	url(r'domain/check/$', api_views.checkDomain, name='domain-check'),
15
-	url(r'rzone/check/$', api_views.checkRzone, name='reversezone-check'),
14
+    url(r'domain/check/$', api_views.checkDomain, name='domain-check'),
15
+    url(r'rzone/check/$', api_views.checkRzone, name='reversezone-check'),
16 16
 
17
-	url(r'roa/$', api_views.getROA, name='get-roa'),
17
+    url(r'roa/$', api_views.getROA, name='get-roa'),
18 18
 
19 19
 ]

+ 184
- 184
api/views.py View File

@@ -19,219 +19,219 @@ import ipaddress
19 19
 @login_required
20 20
 def asblockFreeAS(request):
21 21
 
22
-	ret = {
23
-		"success": False,
24
-		"errorMsg": None,
25
-		"number": -1,
26
-	}
27
-
28
-	try:
29
-		blockName = request.GET.get('block', None)
30
-		if not blockName:
31
-			raise ValidationError("No block given")
32
-
33
-		try:
34
-			mnts = request.user.maintainer_set.all()
35
-			block = ASBlock.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct().get(handle=blockName)
36
-			if block.asblock_set.count() > 0:
37
-				raise ValidationError("AS Block already has sub AS Blocks")
38
-			if block.asnumber_set.count() > 0:
39
-				num = block.asnumber_set.order_by("-number")[0].number + 1
40
-				if num > block.asEnd:
41
-					num = None
42
-					for n in range(block.asBegin, block.asEnd + 1):
43
-						try:
44
-							ASNumber.objects.get(number=n)
45
-						except ASNumber.DoesNotExist:
46
-							num = n
47
-							break
48
-					if not num:
49
-						raise ValidationError("No free AS Number in block")
50
-				ret["number"] = num
51
-			else:
52
-				ret["number"] = block.asBegin
53
-		except ASBlock.DoesNotExist:
54
-			raise ValidationError("Could not get AS Block")
55
-
56
-		ret["success"] = True
57
-	except ValidationError as e:
58
-		ret["errorMsg"] = e.message
59
-	return JsonResponse(ret)
22
+    ret = {
23
+        "success": False,
24
+        "errorMsg": None,
25
+        "number": -1,
26
+    }
27
+
28
+    try:
29
+        blockName = request.GET.get('block', None)
30
+        if not blockName:
31
+            raise ValidationError("No block given")
32
+
33
+        try:
34
+            mnts = request.user.maintainer_set.all()
35
+            block = ASBlock.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct().get(handle=blockName)
36
+            if block.asblock_set.count() > 0:
37
+                raise ValidationError("AS Block already has sub AS Blocks")
38
+            if block.asnumber_set.count() > 0:
39
+                num = block.asnumber_set.order_by("-number")[0].number + 1
40
+                if num > block.asEnd:
41
+                    num = None
42
+                    for n in range(block.asBegin, block.asEnd + 1):
43
+                        try:
44
+                            ASNumber.objects.get(number=n)
45
+                        except ASNumber.DoesNotExist:
46
+                            num = n
47
+                            break
48
+                    if not num:
49
+                        raise ValidationError("No free AS Number in block")
50
+                ret["number"] = num
51
+            else:
52
+                ret["number"] = block.asBegin
53
+        except ASBlock.DoesNotExist:
54
+            raise ValidationError("Could not get AS Block")
55
+
56
+        ret["success"] = True
57
+    except ValidationError as e:
58
+        ret["errorMsg"] = e.message
59
+    return JsonResponse(ret)
60 60
 
61 61
 
62 62
 @login_required
63 63
 def freeSubnet(request):
64 64
 
65
-	ret = {
66
-		"success": False,
67
-		"errorMsg": None,
68
-		"network": None,
69
-	}
70
-
71
-	try:
72
-		parentRangeName = request.GET.get('parentRange', None)
73
-		if not parentRangeName:
74
-			raise ValidationError("No subnet given")
75
-
76
-		parentRange = None
77
-		try:
78
-			mnts = request.user.maintainer_set.all()
79
-			parentRange = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct().get(handle=parentRangeName)
80
-		except InetNum.DoesNotExist:
81
-			raise ValidationError("Parent range does not exist / is not maintained by you")
82
-
83
-		prefixLen = 0
84
-		try:
85
-			prefixLen = request.GET.get("prefixLen", None)
86
-			if not prefixLen:
87
-				if parentRange.protocol == InetNum.IPv4:
88
-					prefixLen = 27
89
-				else:
90
-					prefixLen = 60
91
-			prefixLen = int(prefixLen)
92
-
93
-			if prefixLen < 8 or \
94
-				(parentRange.protocol == InetNum.IPv4 and prefixLen > 32) or \
95
-				(parentRange.protocol == InetNum.IPv6 and prefixLen > 128):
96
-				raise ValidationError("Given prefix length is out of range")
97
-		except ValueError:
98
-			raise ValidationError("PrefixLen is not a number")
99
-
100
-		usableNet = None
101
-		# FIXME: use first biggest usable netblock...
102
-		#biggestNet = parentRange.inetnum_set.order_by("-address")
103
-		#if biggestNet
104
-		#	candidateNet =
105
-		#	and (biggestNet.getNetwork().broadcast_address.:
106
-
107
-		# try using next network in range
108
-		# ordering does not work, as order_by sorts alphabetically
109
-		# ==> the set is short, we can iterate through all. no sense in unpacking the SQL magic
110
-		#biggestNets = parentRange.inetnum_set.order_by("-address")
111
-		biggestNet = None
112
-		for ipRange in parentRange.inetnum_set.all():
113
-			if not biggestNet or ipRange.getNetwork() > biggestNet:
114
-				biggestNet = ipRange.getNetwork()
115
-
116
-		if biggestNet:
117
-			candidateNet = ipaddress.ip_network("%s/%s" % (biggestNet.broadcast_address + 1, biggestNet.prefixlen))
118
-			print("biggest net", biggestNet, "candidate", candidateNet)
119
-			if candidateNet.network_address in parentRange.getNetwork():
120
-				usableNet = candidateNet
121
-
122
-		# check if there are still networks left in range
123
-		if not usableNet:
124
-			# search for free network
125
-			nets = list(parentRange.getNetwork().subnets())
126
-			for subRange in parentRange.inetnum_set.all():
127
-				newNet = None
128
-				for net in nets:
129
-					if subRange.getNetwork().network_address in net:
130
-						newNet = net
131
-				if not newNet:
132
-					# critical error, we want a 500 here
133
-					raise ValueError("Subnet not in range")
134
-
135
-				nets.remove(newNet)
136
-				nets.extend(newNet.address_exclude(subRange.getNetwork()))
137
-
138
-			nets = sorted(nets)
139
-			for net in nets:
140
-				if net.prefixlen <= prefixLen:
141
-					usableNet = net
142
-					break
143
-
144
-		if not usableNet:
145
-			raise ValidationError("No space left in given range")
146
-
147
-		ret["network"] = "%s/%s" % (usableNet.network_address, prefixLen)
148
-		ret["success"] = True
149
-	except ValidationError as e:
150
-		ret["errorMsg"] = e.message
151
-
152
-	return JsonResponse(ret)
65
+    ret = {
66
+        "success": False,
67
+        "errorMsg": None,
68
+        "network": None,
69
+    }
70
+
71
+    try:
72
+        parentRangeName = request.GET.get('parentRange', None)
73
+        if not parentRangeName:
74
+            raise ValidationError("No subnet given")
75
+
76
+        parentRange = None
77
+        try:
78
+            mnts = request.user.maintainer_set.all()
79
+            parentRange = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct().get(handle=parentRangeName)
80
+        except InetNum.DoesNotExist:
81
+            raise ValidationError("Parent range does not exist / is not maintained by you")
82
+
83
+        prefixLen = 0
84
+        try:
85
+            prefixLen = request.GET.get("prefixLen", None)
86
+            if not prefixLen:
87
+                if parentRange.protocol == InetNum.IPv4:
88
+                    prefixLen = 27
89
+                else:
90
+                    prefixLen = 60
91
+            prefixLen = int(prefixLen)
92
+
93
+            if prefixLen < 8 or \
94
+                (parentRange.protocol == InetNum.IPv4 and prefixLen > 32) or \
95
+                (parentRange.protocol == InetNum.IPv6 and prefixLen > 128):
96
+                raise ValidationError("Given prefix length is out of range")
97
+        except ValueError:
98
+            raise ValidationError("PrefixLen is not a number")
99
+
100
+        usableNet = None
101
+        # FIXME: use first biggest usable netblock...
102
+        #biggestNet = parentRange.inetnum_set.order_by("-address")
103
+        #if biggestNet
104
+        #    candidateNet =
105
+        #    and (biggestNet.getNetwork().broadcast_address.:
106
+
107
+        # try using next network in range
108
+        # ordering does not work, as order_by sorts alphabetically
109
+        # ==> the set is short, we can iterate through all. no sense in unpacking the SQL magic
110
+        #biggestNets = parentRange.inetnum_set.order_by("-address")
111
+        biggestNet = None
112
+        for ipRange in parentRange.inetnum_set.all():
113
+            if not biggestNet or ipRange.getNetwork() > biggestNet:
114
+                biggestNet = ipRange.getNetwork()
115
+
116
+        if biggestNet:
117
+            candidateNet = ipaddress.ip_network("%s/%s" % (biggestNet.broadcast_address + 1, biggestNet.prefixlen))
118
+            print("biggest net", biggestNet, "candidate", candidateNet)
119
+            if candidateNet.network_address in parentRange.getNetwork():
120
+                usableNet = candidateNet
121
+
122
+        # check if there are still networks left in range
123
+        if not usableNet:
124
+            # search for free network
125
+            nets = list(parentRange.getNetwork().subnets())
126
+            for subRange in parentRange.inetnum_set.all():
127
+                newNet = None
128
+                for net in nets:
129
+                    if subRange.getNetwork().network_address in net:
130
+                        newNet = net
131
+                if not newNet:
132
+                    # critical error, we want a 500 here
133
+                    raise ValueError("Subnet not in range")
134
+
135
+                nets.remove(newNet)
136
+                nets.extend(newNet.address_exclude(subRange.getNetwork()))
137
+
138
+            nets = sorted(nets)
139
+            for net in nets:
140
+                if net.prefixlen <= prefixLen:
141
+                    usableNet = net
142
+                    break
143
+
144
+        if not usableNet:
145
+            raise ValidationError("No space left in given range")
146
+
147
+        ret["network"] = "%s/%s" % (usableNet.network_address, prefixLen)
148
+        ret["success"] = True
149
+    except ValidationError as e:
150
+        ret["errorMsg"] = e.message
151
+
152
+    return JsonResponse(ret)
153 153
 
154 154
 
155 155
 @login_required
156 156
 def getSubnet(request):
157 157
 
158
-	ret = {
159
-		"success": False,
160
-		"errorMsg": None,
161
-		"network": None,
162
-	}
158
+    ret = {
159
+        "success": False,
160
+        "errorMsg": None,
161
+        "network": None,
162
+    }
163 163
 
164
-	try:
165
-		netName = request.GET.get('net', None)
166
-		mnts = request.user.maintainer_set.all()
167
-		nets = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct()
168
-		net = nets.get(handle=netName)
164
+    try:
165
+        netName = request.GET.get('net', None)
166
+        mnts = request.user.maintainer_set.all()
167
+        nets = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct()
168
+        net = nets.get(handle=netName)
169 169
 
170
-		ret["success"] = True
171
-		ret["network"] = net.prefix()
172
-	except InetNum.DoesNotExist:
173
-		ret["errorMsg"] = "Chosen network does not exist"
170
+        ret["success"] = True
171
+        ret["network"] = net.prefix()
172
+    except InetNum.DoesNotExist:
173
+        ret["errorMsg"] = "Chosen network does not exist"
174 174
 
175
-	return JsonResponse(ret)
175
+    return JsonResponse(ret)
176 176
 
177 177
 
178 178
 @login_required
179 179
 def checkDomain(request):
180
-	ret = {
181
-		"success": False,
182
-		"errorMsg": None,
183
-		"domain": None,
184
-		"result": None,
185
-	}
180
+    ret = {
181
+        "success": False,
182
+        "errorMsg": None,
183
+        "domain": None,
184
+        "result": None,
185
+    }
186 186
 
187
-	try:
188
-		domainName = Domain.fixName(request.GET.get('domain', ''))
189
-		domain = Domain.objects.get(name=domainName)
190
-		#if not domain.canEdit(request.user):
191
-		#	raise Domain.DoesNotExist()
187
+    try:
188
+        domainName = Domain.fixName(request.GET.get('domain', ''))
189
+        domain = Domain.objects.get(name=domainName)
190
+        #if not domain.canEdit(request.user):
191
+        #    raise Domain.DoesNotExist()
192 192
 
193
-		ret["success"] = True
194
-		ret["domain"] = domain.name
195
-		# FIXME: change this if we ever have more than one...
196
-		ret["result"] = helperCheckDomain(domain.name, TLD_NAMESERVERS[0], domain.nameservers.all())
197
-	except Domain.DoesNotExist:
198
-		ret["errorMsg"] = "Domain does not exist"
193
+        ret["success"] = True
194
+        ret["domain"] = domain.name
195
+        # FIXME: change this if we ever have more than one...
196
+        ret["result"] = helperCheckDomain(domain.name, TLD_NAMESERVERS[0], domain.nameservers.all())
197
+    except Domain.DoesNotExist:
198
+        ret["errorMsg"] = "Domain does not exist"
199 199
 
200
-	return JsonResponse(ret)
200
+    return JsonResponse(ret)
201 201
 
202 202
 
203 203
 @login_required
204 204
 def checkRzone(request):
205
-	ret = {
206
-		"success": False,
207
-		"errorMsg": None,
208
-		"domain": None,
209
-		"result": None,
210
-	}
205
+    ret = {
206
+        "success": False,
207
+        "errorMsg": None,
208
+        "domain": None,
209
+        "result": None,
210
+    }
211 211
 
212
-	try:
213
-		rzonePk = int(request.GET.get('domain', ''))
214
-		rzone = ReverseZone.objects.get(pk=rzonePk)
215
-		#if not rzone.canEdit(request.user):
216
-		#	raise ReverseZone.DoesNotExist()
212
+    try:
213
+        rzonePk = int(request.GET.get('domain', ''))
214
+        rzone = ReverseZone.objects.get(pk=rzonePk)
215
+        #if not rzone.canEdit(request.user):
216
+        #    raise ReverseZone.DoesNotExist()
217 217
 
218
-		ret["success"] = True
219
-		# FIXME: change this if we ever have more than one...
220
-		ret["result"] = helperCheckDomain(rzone.getZone(), TLD_NAMESERVERS[0], rzone.nameservers.all())
221
-	except (ReverseZone.DoesNotExist, ValueError):
222
-		ret["errorMsg"] = "ReverseZone does not exist"
218
+        ret["success"] = True
219
+        # FIXME: change this if we ever have more than one...
220
+        ret["result"] = helperCheckDomain(rzone.getZone(), TLD_NAMESERVERS[0], rzone.nameservers.all())
221
+    except (ReverseZone.DoesNotExist, ValueError):
222
+        ret["errorMsg"] = "ReverseZone does not exist"
223 223
 
224
-	return JsonResponse(ret)
224
+    return JsonResponse(ret)
225 225
 
226 226
 
227 227
 def getROA(request):
228
-	roa = {}
229
-	for asn in ASNumber.objects.all():
230
-		nets = []
231
-		for net in asn.inetnum_set.all():
232
-			nets.append(net.prefix())
228
+    roa = {}
229
+    for asn in ASNumber.objects.all():
230
+        nets = []
231
+        for net in asn.inetnum_set.all():
232
+            nets.append(net.prefix())
233 233
 
234
-		if nets:
235
-			roa[asn.number] = nets
234
+        if nets:
235
+            roa[asn.number] = nets
236 236
 
237
-	return JsonResponse(roa)
237
+    return JsonResponse(roa)

+ 153
- 151
bin/dns-sync View File

@@ -25,179 +25,181 @@ __VERSION__ = '0.1'
25 25
 
26 26
 
27 27
 def _parser():
28
-	parser = argparse.ArgumentParser(
29
-	             #prog='foo',
30
-	             #description='do some awesome foo',
31
-	)
28
+    parser = argparse.ArgumentParser()
32 29
 
33
-	parser.add_argument("--pdns-host", default="127.0.0.1", help="PDNS host")
34
-	parser.add_argument("--pdns-port", default=8081, help="PDNS port")
35
-	parser.add_argument("-c", "--config", default=None, type=argparse.FileType("r"), help="Path to config file (default path: ./dns-sync.conf, /etc/dns-sync.conf)")
36
-	parser.add_argument("--api-key", default=None, help="PDNS API key")
37
-	parser.add_argument("--version", action="version", version="%(prog)s " + __VERSION__)
30
+    parser.add_argument("--pdns-host", default="127.0.0.1", help="PDNS host")
31
+    parser.add_argument("--pdns-port", default=8081, help="PDNS port")
32
+    parser.add_argument("-c", "--config", default=None, type=argparse.FileType("r"), help="Path to config file (default path: ./dns-sync.conf, /etc/dns-sync.conf)")
33
+    parser.add_argument("--api-key", default=None, help="PDNS API key")
34
+    parser.add_argument("--version", action="version", version="%(prog)s " + __VERSION__)
35
+
36
+    return parser
38 37
 
39
-	return parser
40 38
 
41 39
 def mergeDomains(zoneData, pdnsData):
42
-	rrAdd = []
43
-	rrData = pdnsData['rrsets']
44
-
45
-	for domain, rrType, records in zoneData:
46
-		found = False
47
-		pdnsDom = list(filter(lambda _x: _x['name'] == domain and _x['type'] == rrType, rrData))
48
-		if len(pdnsDom) > 0:
49
-			rrSet = set(_x['content'] for _x in pdnsDom[0]['records'])
50
-			if rrSet == set(records):
51
-				found = True
52
-
53
-		if not found:
54
-			# new domain!
55
-			rrAdd.append({
56
-				"name": domain,
57
-				"type": rrType,
58
-				"ttl": 60*60,
59
-				"changetype": "REPLACE",
60
-				"records": [{"content": record, "disabled": False} for record in records],
61
-			})
62
-
63
-	return rrAdd
40
+    rrAdd = []
41
+    rrData = pdnsData['rrsets']
42
+
43
+    for domain, rrType, records in zoneData:
44
+        found = False
45
+        pdnsDom = list(filter(lambda _x: _x['name'] == domain and _x['type'] == rrType, rrData))
46
+        if len(pdnsDom) > 0:
47
+            rrSet = set(_x['content'].lower() for _x in pdnsDom[0]['records'])
48
+            if rrSet == set(record.lower() for record in records):
49
+                found = True
50
+
51
+        if not found:
52
+            # new domain!
53
+            rrAdd.append({
54
+                "name": domain,
55
+                "type": rrType,
56
+                "ttl": 60*60,
57
+                "changetype": "REPLACE",
58
+                "records": [{"content": record, "disabled": False} for record in records],
59
+            })
60
+
61
+    return rrAdd
64 62
 
65 63
 
66 64
 def removeOldDomains(zoneData, pdnsData):
67
-	rrDel = []
68
-
69
-	#print("zone data", zoneData)
70
-	#print("pdnsData", pdnsData)
71
-	for entry in pdnsData['rrsets']:
72
-		# search for name/type in domain dict. if non-existtant mark for deletion
73
-		# this could be much more efficient with a dict! name: [rrset...]
74
-		if not any(entry['name'] == _x[0] and entry['type'] == _x[1] for _x in zoneData):
75
-			rrDel.append({
76
-				"changetype": "DELETE",
77
-				"name": entry["name"],
78
-				"type": entry["type"],
79
-			})
80
-
81
-	return rrDel
65
+    rrDel = []
66
+
67
+    #print("zone data", zoneData)
68
+    #print("pdnsData", pdnsData)
69
+    for entry in pdnsData['rrsets']:
70
+        # search for name/type in domain dict. if non-existtant mark for deletion
71
+        # this could be much more efficient with a dict! name: [rrset...]
72
+        if not any(entry['name'] == _x[0] and entry['type'] == _x[1] for _x in zoneData):
73
+            rrDel.append({
74
+                "changetype": "DELETE",
75
+                "name": entry["name"],
76
+                "type": entry["type"],
77
+            })
78
+
79
+    return rrDel
80
+
82 81
 
83 82
 def handleNameserver(ns, servers, usedNameservers, domains):
84
-	servers.append(ns.name)
83
+    servers.append(ns.name)
85 84
 
86
-	if ns.name not in usedNameservers and (ns.glueIPv4 or ns.glueIPv6):
87
-		usedNameservers.append(ns.name)
88
-		if ns.glueIPv4:
89
-			domains.append((ns.name, "A", [ns.glueIPv4]))
90
-		if ns.glueIPv6:
91
-			domains.append((ns.name, "AAAA", [ns.glueIPv6]))
85
+    if ns.name not in usedNameservers and (ns.glueIPv4 or ns.glueIPv6):
86
+        usedNameservers.append(ns.name)
87
+        if ns.glueIPv4:
88
+            domains.append((ns.name, "A", [ns.glueIPv4]))
89
+        if ns.glueIPv6:
90
+            domains.append((ns.name, "AAAA", [ns.glueIPv6]))
92 91
 
93 92
 
94 93
 def getDomainsFromQueryset(qs, zone, glueRecords, usedNameservers, v4reverse=False):
95
-	for domain in qs:
96
-		servers = []
97
-		for ns in domain.nameservers.all():
98
-			servers.append(ns.name)
99
-
100
-			if ns.name not in usedNameservers and (ns.glueIPv4 or ns.glueIPv6):
101
-				usedNameservers.append(ns.name)
102
-				if ns.glueIPv4:
103
-					glueRecords.append((ns.name, "A", [ns.glueIPv4]))
104
-				if ns.glueIPv6:
105
-					glueRecords.append((ns.name, "AAAA", [ns.glueIPv6]))
106
-
107
-		zone.append((domain.getZone(), "NS", servers))
108
-
109
-		if v4reverse:
110
-			# for ipv4 reverse we have to do some extra work in case of classless delegations
111
-			# see RFC2317
112
-			net = domain.parentNet.getNetwork()
113
-			if net.prefixlen % 8 != 0:
114
-				revZone = domain.getZone()
115
-				parts = str(net.network_address).split(".")
116
-				baseZone = ".".join(reversed(parts[:net.prefixlen // 8])) + ".in-addr.arpa."
117
-				startNo = int(parts[net.prefixlen // 8])
118
-				lenExp = 8 - (net.prefixlen % 8)
119
-
120
-				for i in range(2 ** lenExp):
121
-					no = startNo + i
122
-					zone.append(("%d.%s" % (no, baseZone), "CNAME", ["%d.%s" % (no, revZone)]))
94
+    for domain in qs:
95
+        servers = []
96
+        for ns in domain.nameservers.all():
97
+            servers.append(ns.name)
98
+
99
+            if ns.name not in usedNameservers and (ns.glueIPv4 or ns.glueIPv6):
100
+                usedNameservers.append(ns.name)
101
+                if ns.glueIPv4:
102
+                    glueRecords.append((ns.name, "A", [ns.glueIPv4]))
103
+                if ns.glueIPv6:
104
+                    glueRecords.append((ns.name, "AAAA", [ns.glueIPv6]))
105
+
106
+        zone.append((domain.getZone(), "NS", servers))
107
+
108
+        if domain.ds_records:
109
+            zone.append((domain.getZone(), "DS", domain.ds_records.split("\n")))
110
+
111
+        if v4reverse:
112
+            # for ipv4 reverse we have to do some extra work in case of classless delegations
113
+            # see RFC2317
114
+            net = domain.parentNet.getNetwork()
115
+            if net.prefixlen % 8 != 0:
116
+                revZone = domain.getZone()
117
+                parts = str(net.network_address).split(".")
118
+                baseZone = ".".join(reversed(parts[:net.prefixlen // 8])) + ".in-addr.arpa."
119
+                startNo = int(parts[net.prefixlen // 8])
120
+                lenExp = 8 - (net.prefixlen % 8)
121
+
122
+                for i in range(2 ** lenExp):
123
+                    no = startNo + i
124
+                    zone.append(("%d.%s" % (no, baseZone), "CNAME", ["%d.%s" % (no, revZone)]))
125
+
123 126
 
124 127
 def mergeDomainsWithPdns(s, args, zone, zoneData, protectedRecords=[]):
125
-	url = "http://%s:%s/api/v1/servers/localhost/zones/%s" % (args.pdns_host, args.pdns_port, zone,)
126
-	pdnsData = s.get(url).json()
128
+    url = "http://%s:%s/api/v1/servers/localhost/zones/%s" % (args.pdns_host, args.pdns_port, zone,)
129
+    pdnsData = s.get(url).json()
127 130
 
128
-	baseProtectedRecords = [
129
-		(zone, "NS", []),
130
-		(zone, "SOA", []),
131
-	]
131
+    baseProtectedRecords = [
132
+        (zone, "NS", []),
133
+        (zone, "SOA", []),
134
+    ]
132 135
 
133
-	# add dn. (NS + glue Nameservers)
134
-	newDomains = mergeDomains(zoneData, pdnsData)
135
-	print("Add/replace", newDomains)
136
+    # add dn. (NS + glue Nameservers)
137
+    newDomains = mergeDomains(zoneData, pdnsData)
138
+    print("Add/replace", newDomains)
136 139
 
137
-	if len(newDomains) > 0:
138
-		r = s.patch(url, data=json.dumps({'rrsets': newDomains}))
139
-		if r.status_code != 204:
140
-			raise RuntimeError("Could not update records in powerdns, API returned %d" % r.status_code)
140
+    if len(newDomains) > 0:
141
+        r = s.patch(url, data=json.dumps({'rrsets': newDomains}))
142
+        if r.status_code != 204:
143
+            raise RuntimeError("Could not update records in powerdns, API returned %d" % r.status_code)
141 144
 
142
-	delDomains = removeOldDomains(zoneData + protectedRecords + baseProtectedRecords, pdnsData)
143
-	print("Del", delDomains)
144
-	r = s.patch(url, data=json.dumps({'rrsets': delDomains}))
145
-	if r.status_code != 204:
146
-		raise RuntimeError("Could not update records in powerdns, API returned %d" % r.status_code)
145
+    delDomains = removeOldDomains(zoneData + protectedRecords + baseProtectedRecords, pdnsData)
146
+    print("Del", delDomains)
147
+    r = s.patch(url, data=json.dumps({'rrsets': delDomains}))
148
+    if r.status_code != 204:
149
+        raise RuntimeError("Could not update records in powerdns, API returned %d" % r.status_code)
147 150
 
148 151
 
149 152
 def main():
150
-	parser = _parser()
151
-	args = parser.parse_args()
152
-
153
-	config = configparser.ConfigParser()
154
-	if args.config:
155
-		config.read_file(args.config)
156
-	else:
157
-		config.read([os.path.join(os.path.abspath(__file__), "dns-sync.conf"), "/etc/dns-sync.conf"])
158
-		#print(config)
159
-		#print(config.get("DEFAULT", "api-key"))
160
-		#print(config.has_section("DEFAULT"), config.has_option("DEFAULT", "api-key"))
161
-
162
-	if args.api_key:
163
-		config["DEFAULT"]["api-key"] = args.api_key
164
-
165
-	if not config.has_option("DEFAULT", "api-key"):
166
-		print("Error: Could not find api-key (not present in config under [DEFAULT]; not given via command line)", file=sys.stderr)
167
-		sys.exit(2)
168
-
169
-	s = requests.Session()
170
-	s.headers = {
171
-		'X-API-Key': config.get("DEFAULT", "api-key"),
172
-		'Accept': 'application/json'
173
-	}
174
-
175
-	domains = []
176
-	rzone4 = []
177
-	rzone6 = []
178
-	usedNameservers = []
179
-
180
-	# assenble domain data
181
-	# dn.
182
-	qs = Domain.objects.annotate(nsCount=Count('nameservers')).filter(nsCount__gt=0).order_by("name")
183
-	getDomainsFromQueryset(qs, domains, domains, usedNameservers)
184
-
185
-	# reverse zones
186
-	qs = ReverseZone.objects.annotate(nsCount=Count('nameservers')).filter(nsCount__gt=0).order_by("parentNet__address")
187
-	qs4 = filter(lambda _revz: _revz.getNetwork().version == 4, qs)
188
-	qs6 = filter(lambda _revz: _revz.getNetwork().version == 6, qs)
189
-	getDomainsFromQueryset(qs4, rzone4, domains, usedNameservers, v4reverse=True)
190
-	getDomainsFromQueryset(qs6, rzone6, domains, usedNameservers)
191
-
192
-	#print("dn.", domains)
193
-	#print("v4", rzone4)
194
-	#print("v6", rzone6)
195
-
196
-	mergeDomainsWithPdns(s, args, "dn.", domains)
197
-	mergeDomainsWithPdns(s, args, "10.in-addr.arpa.", rzone4)
198
-	mergeDomainsWithPdns(s, args, "3.2.7.c.3.a.d.f.ip6.arpa.", rzone6)
199
-
153
+    parser = _parser()
154
+    args = parser.parse_args()
155
+
156
+    config = configparser.ConfigParser()
157
+    if args.config:
158
+        config.read_file(args.config)
159
+    else:
160
+        config.read([os.path.join(os.path.abspath(__file__), "dns-sync.conf"), "/etc/dns-sync.conf"])
161
+        #print(config)
162
+        #print(config.get("DEFAULT", "api-key"))
163
+        #print(config.has_section("DEFAULT"), config.has_option("DEFAULT", "api-key"))
164
+
165
+    if args.api_key:
166
+        config["DEFAULT"]["api-key"] = args.api_key
167
+
168
+    if not config.has_option("DEFAULT", "api-key"):
169
+        print("Error: Could not find api-key (not present in config under [DEFAULT]; not given via command line)", file=sys.stderr)
170
+        sys.exit(2)
171
+
172
+    s = requests.Session()
173
+    s.headers = {
174
+        'X-API-Key': config.get("DEFAULT", "api-key"),
175
+        'Accept': 'application/json'
176
+    }
177
+
178
+    domains = []
179
+    rzone4 = []
180
+    rzone6 = []
181
+    usedNameservers = []
182
+
183
+    # assenble domain data
184
+    # dn.
185
+    qs = Domain.objects.annotate(nsCount=Count('nameservers')).filter(nsCount__gt=0).order_by("name")
186
+    getDomainsFromQueryset(qs, domains, domains, usedNameservers)
187
+
188
+    # reverse zones
189
+    qs = ReverseZone.objects.annotate(nsCount=Count('nameservers')).filter(nsCount__gt=0).order_by("parentNet__address")
190
+    qs4 = filter(lambda _revz: _revz.getNetwork().version == 4, qs)
191
+    qs6 = filter(lambda _revz: _revz.getNetwork().version == 6, qs)
192
+    getDomainsFromQueryset(qs4, rzone4, domains, usedNameservers, v4reverse=True)
193
+    getDomainsFromQueryset(qs6, rzone6, domains, usedNameservers)
194
+
195
+    #print("dn.", domains)
196
+    #print("v4", rzone4)
197
+    #print("v6", rzone6)
198
+
199
+    mergeDomainsWithPdns(s, args, "dn.", domains)
200
+    mergeDomainsWithPdns(s, args, "10.in-addr.arpa.", rzone4)
201
+    mergeDomainsWithPdns(s, args, "3.2.7.c.3.a.d.f.ip6.arpa.", rzone6)
200 202
 
201 203
 
202 204
 if __name__ == '__main__':
203
-	main()
205
+    main()

+ 97
- 97
bin/import-data View File

@@ -24,107 +24,107 @@ from whoisdb.models import ASBlock, ASNumber, Contact, Maintainer, InetNum
24 24
 __VERSION__ = '0.1'
25 25
 
26 26
 def _parser():
27
-	parser = argparse.ArgumentParser(
28
-	             #prog='foo',
29
-	             #description='do some awesome foo',
30
-	)
27
+    parser = argparse.ArgumentParser(
28
+                 #prog='foo',
29
+                 #description='do some awesome foo',
30
+    )
31 31
 
32
-	#parser.add_argument("-p", "--port", default=2323, type=int, help="Your port")
33
-	#parser.add_argument("-v", "--verbose", default=False, action="store_true", help="Be more verbose")
34
-	parser.add_argument("-j", "--json", type=argparse.FileType('r'), required=True, help="Path to json file")
32
+    #parser.add_argument("-p", "--port", default=2323, type=int, help="Your port")
33
+    #parser.add_argument("-v", "--verbose", default=False, action="store_true", help="Be more verbose")
34
+    parser.add_argument("-j", "--json", type=argparse.FileType('r'), required=True, help="Path to json file")
35 35
 
36
-	parser.add_argument("--version", action="version", version="%(prog)s " + __VERSION__)
36
+    parser.add_argument("--version", action="version", version="%(prog)s " + __VERSION__)
37 37
 
38
-	return parser
38
+    return parser
39 39
 
40 40
 def main():
41
-	parser = _parser()
42
-	args = parser.parse_args()
43
-
44
-	defContact = Contact.objects.get(handle="UNKNOWN-DN")
45
-	defMnt = Maintainer.objects.get(handle="DARKNET-MNT")
46
-	blocks = [
47
-		ASBlock.objects.get(handle="TRA1-ASB"),
48
-		ASBlock.objects.get(handle="UAB1-ASB")
49
-	]
50
-	ranges = [
51
-		InetNum.objects.get(handle="TRA1-NET"),
52
-		InetNum.objects.get(handle="DEF4-NET"),
53
-		InetNum.objects.get(handle="DEF6-NET"),
54
-		InetNum.objects.get(handle="MISC6-NET"),
55
-		InetNum.objects.get(handle="LTRA1-NET"),
56
-		InetNum.objects.get(handle="MAIN4-NET"),
57
-	]
58
-
59
-
60
-	data = json.load(args.json)
61
-	print(data.keys())
62
-	for k, v in data["handles"].items():
63
-		print(k, v["person"])
64
-	for asn in data["as"]:
65
-		print(asn)
66
-		if ASNumber.objects.filter(number=asn["number"]).count() > 0:
67
-			continue
68
-
69
-		obj = ASNumber(number=asn["number"])
70
-		block = None
71
-		for block in blocks:
72
-			if obj.number >= block.asBegin and obj.number <= block.asEnd:
73
-				obj.asblock = block
74
-				break
75
-		else:
76
-			raise ValueError("AS %d does not fit a block" % asn["number"])
77
-
78
-		if len(asn['admin_c']) >= 1:
79
-			name = data["handles"][asn["admin_c"][0]]["person"]
80
-			obj.name = "Imported AS of %s" % name
81
-			obj.handle = ASNumber.genGenericHandle(re.sub("[^a-zA-Z0-9 ]", "", name))
82
-		else:
83
-			obj.name = "Imported AS without admin info"
84
-			obj.handle = ASNumber.genGenericHandle("Unknown Imported AS")
85
-		obj.description = "Object has been imported from old DB and has not yet been edited"
86
-		obj.save()
87
-		obj.mnt_by.add(defMnt)
88
-		obj.admin_c.add(defContact)
89
-		obj.save()
90
-
91
-	for net in data["network"]:
92
-		print(net)
93
-		network = ipaddress.ip_network(net["prefix"])
94
-
95
-		if InetNum.objects.filter(address=str(network.network_address), netmask=network.prefixlen).count() > 0:
96
-			continue
97
-
98
-		origin = None
99
-		if net["origin"]:
100
-			origin = ASNumber.objects.get(number=net["origin"])
101
-		obj = InetNum(address=str(network.network_address), netmask=network.prefixlen)
102
-		obj.protocol = InetNum.IPv4 if network.version == 4 else InetNum.IPv6
103
-
104
-		x = list(filter(lambda _x: _x['number'] == net["origin"], data["as"]))
105
-		if len(x) > 0 and x[0]["admin_c"]:
106
-			name = data["handles"][x[0]["admin_c"][0]]["person"]
107
-			obj.name = "Imported Network of %s" % name
108
-			obj.handle = InetNum.genGenericHandle(re.sub("[^a-zA-Z0-9 ]", "", name))
109
-		else:
110
-			obj.name = "Imported Network without admin info"
111
-			obj.handle = InetNum.genGenericHandle("Unknown Imported Network")
112
-		obj.description = "Object has been imported from old DB and has not yet been edited"
113
-
114
-		for r in ranges:
115
-			if network.network_address in r.getNetwork():
116
-				obj.parent_range = r
117
-				break
118
-		else:
119
-			raise ValueError("%s did not fit in any netblock" % network)
120
-
121
-		obj.save()
122
-
123
-		obj.mnt_by.add(defMnt)
124
-		obj.admin_c.add(defContact)
125
-		if origin:
126
-			obj.origin_as.add(origin)
127
-		obj.save()
41
+    parser = _parser()
42
+    args = parser.parse_args()
43
+
44
+    defContact = Contact.objects.get(handle="UNKNOWN-DN")
45
+    defMnt = Maintainer.objects.get(handle="DARKNET-MNT")
46
+    blocks = [
47
+        ASBlock.objects.get(handle="TRA1-ASB"),
48
+        ASBlock.objects.get(handle="UAB1-ASB")
49
+    ]
50
+    ranges = [
51
+        InetNum.objects.get(handle="TRA1-NET"),
52
+        InetNum.objects.get(handle="DEF4-NET"),
53
+        InetNum.objects.get(handle="DEF6-NET"),
54
+        InetNum.objects.get(handle="MISC6-NET"),
55
+        InetNum.objects.get(handle="LTRA1-NET"),
56
+        InetNum.objects.get(handle="MAIN4-NET"),
57
+    ]
58
+
59
+
60
+    data = json.load(args.json)
61
+    print(data.keys())
62
+    for k, v in data["handles"].items():
63
+        print(k, v["person"])
64
+    for asn in data["as"]:
65
+        print(asn)
66
+        if ASNumber.objects.filter(number=asn["number"]).count() > 0:
67
+            continue
68
+
69
+        obj = ASNumber(number=asn["number"])
70
+        block = None
71
+        for block in blocks:
72
+            if obj.number >= block.asBegin and obj.number <= block.asEnd:
73
+                obj.asblock = block
74
+                break
75
+        else:
76
+            raise ValueError("AS %d does not fit a block" % asn["number"])
77
+
78
+        if len(asn['admin_c']) >= 1:
79
+            name = data["handles"][asn["admin_c"][0]]["person"]
80
+            obj.name = "Imported AS of %s" % name
81
+            obj.handle = ASNumber.genGenericHandle(re.sub("[^a-zA-Z0-9 ]", "", name))
82
+        else:
83
+            obj.name = "Imported AS without admin info"
84
+            obj.handle = ASNumber.genGenericHandle("Unknown Imported AS")
85
+        obj.description = "Object has been imported from old DB and has not yet been edited"
86
+        obj.save()
87
+        obj.mnt_by.add(defMnt)
88
+        obj.admin_c.add(defContact)
89
+        obj.save()
90
+
91
+    for net in data["network"]:
92
+        print(net)
93
+        network = ipaddress.ip_network(net["prefix"])
94
+
95
+        if InetNum.objects.filter(address=str(network.network_address), netmask=network.prefixlen).count() > 0:
96
+            continue
97
+
98
+        origin = None
99
+        if net["origin"]:
100
+            origin = ASNumber.objects.get(number=net["origin"])
101
+        obj = InetNum(address=str(network.network_address), netmask=network.prefixlen)
102
+        obj.protocol = InetNum.IPv4 if network.version == 4 else InetNum.IPv6
103
+
104
+        x = list(filter(lambda _x: _x['number'] == net["origin"], data["as"]))
105
+        if len(x) > 0 and x[0]["admin_c"]:
106
+            name = data["handles"][x[0]["admin_c"][0]]["person"]
107
+            obj.name = "Imported Network of %s" % name
108
+            obj.handle = InetNum.genGenericHandle(re.sub("[^a-zA-Z0-9 ]", "", name))
109
+        else:
110
+            obj.name = "Imported Network without admin info"
111
+            obj.handle = InetNum.genGenericHandle("Unknown Imported Network")
112
+        obj.description = "Object has been imported from old DB and has not yet been edited"
113
+
114
+        for r in ranges:
115
+            if network.network_address in r.getNetwork():
116
+                obj.parent_range = r
117
+                break
118
+        else:
119
+            raise ValueError("%s did not fit in any netblock" % network)
120
+
121
+        obj.save()
122
+
123
+        obj.mnt_by.add(defMnt)
124
+        obj.admin_c.add(defContact)
125
+        if origin:
126
+            obj.origin_as.add(origin)
127
+        obj.save()
128 128
 
129 129
 if __name__ == '__main__':
130
-	main()
130
+    main()

+ 4
- 4
bin/markdown-to-html View File

@@ -12,11 +12,11 @@ import os
12 12
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
13 13
 
14 14
 files = [
15
-	("templates/help/faq.md", "templates/help/faq.html"),
15
+    ("templates/help/faq.md", "templates/help/faq.html"),
16 16
 ]
17 17
 
18 18
 for srcFile, dstFile in files:
19
-	srcFile = os.path.join(BASE_DIR, srcFile)
20
-	dstFile = os.path.join(BASE_DIR, dstFile)
21
-	markdown.markdownFromFile(open(srcFile, "rb"), open(dstFile, "wb"), extensions=[TocExtension(baselevel=1, permalink=True)])
19
+    srcFile = os.path.join(BASE_DIR, srcFile)
20
+    dstFile = os.path.join(BASE_DIR, dstFile)
21
+    markdown.markdownFromFile(open(srcFile, "rb"), open(dstFile, "wb"), extensions=[TocExtension(baselevel=1, permalink=True)])
22 22
 

+ 14
- 14
bin/roa-export View File

@@ -22,26 +22,26 @@ from whoisdb.models import ASNumber
22 22
 __VERSION__ = '0.1'
23 23
 
24 24
 def _parser():
25
-	parser = argparse.ArgumentParser()
25
+    parser = argparse.ArgumentParser()
26 26
 
27
-	parser.add_argument("--version", action="version", version="%(prog)s " + __VERSION__)
27
+    parser.add_argument("--version", action="version", version="%(prog)s " + __VERSION__)
28 28
 
29
-	return parser
29
+    return parser
30 30
 
31 31
 def main():
32
-	parser = _parser()
33
-	parser.parse_args()
32
+    parser = _parser()
33
+    parser.parse_args()
34 34
 
35
-	roa = {}
36
-	for asn in ASNumber.objects.all():
37
-		nets = []
38
-		for net in asn.inetnum_set.all():
39
-			nets.append(net.prefix())
35
+    roa = {}
36
+    for asn in ASNumber.objects.all():
37
+        nets = []
38
+        for net in asn.inetnum_set.all():
39
+            nets.append(net.prefix())
40 40
 
41
-		if nets:
42
-			roa[asn.number] = nets
41
+        if nets:
42
+            roa[asn.number] = nets
43 43
 
44
-	print(json.dumps(roa))
44
+    print(json.dumps(roa))
45 45
 
46 46
 if __name__ == '__main__':
47
-	main()
47
+    main()

+ 65
- 65
bin/whoisd View File

@@ -24,84 +24,84 @@ log = logging.getLogger("whoisd")
24 24
 __VERSION__ = '0.1'
25 25
 
26 26
 class WhoisHandler(socketserver.BaseRequestHandler):
27
-	header = "% This is the DARKNET database query service.\n" \
28
-			"% The objects should be in something like RPSL format.\n" \
29
-			"%\n" \
30
-			"% The DARKNET database is subject to terms and conditions.\n" \
31
-			"% Mostly these are \"be nice\" and \"don't knowingly break things\".\n" \
32
-			"\n"
33
-
34
-	def handle(self):
35
-		self.request.sendall(self.header.encode())
36
-		line = self.request.recv(1024)
37
-		line = line.split(b"\n", 2)[0].strip()
38
-		print("Request object is %s" % line)
39
-		log.info("Request by %s for %s" % (self.request.getpeername()[0], line))
40
-		self.request.send(b"\n")
41
-
42
-		objs = findInDatabase(line.decode())
43
-		if len(objs) > 0:
44
-			self.request.sendall(("%% %d result%s\n" % (len(objs), "" if len(objs)==1 else "s")).encode())
45
-			for obj in objs:
46
-				self.sendObject(obj)
47
-		else:
48
-			self.request.sendall(b"%% NOT FOUND\n\n")
49
-
50
-	def sendObject(self, obj):
51
-		result = [
52
-			"",
53
-			"%% Object %s (%s)" % (obj, obj.getClassName()),
54
-			""
55
-		]
56
-
57
-		for field, value in getWhoisObjectFields(obj, False):
58
-			fieldName = field.lower().replace(" ", "-") + ":"
59
-			if not value:
60
-				value = ""
61
-
62
-			if hasattr(value, "through"):
63
-				for v in value.all():
64
-					result.append("%-16s %s" % (fieldName, v))
65
-			else:
66
-				result.append("%-16s %s" % (fieldName, value))
67
-		result.extend(["", ""])
68
-
69
-		self.request.sendall("\n".join(result).encode())
27
+    header = "% This is the DARKNET database query service.\n" \
28
+            "% The objects should be in something like RPSL format.\n" \
29
+            "%\n" \
30
+            "% The DARKNET database is subject to terms and conditions.\n" \
31
+            "% Mostly these are \"be nice\" and \"don't knowingly break things\".\n" \
32
+            "\n"
33
+
34
+    def handle(self):
35
+        self.request.sendall(self.header.encode())
36
+        line = self.request.recv(1024)
37
+        line = line.split(b"\n", 2)[0].strip()
38
+        print("Request object is %s" % line)
39
+        log.info("Request by %s for %s" % (self.request.getpeername()[0], line))
40
+        self.request.send(b"\n")
41
+
42
+        objs = findInDatabase(line.decode())
43
+        if len(objs) > 0:
44
+            self.request.sendall(("%% %d result%s\n" % (len(objs), "" if len(objs)==1 else "s")).encode())
45
+            for obj in objs:
46
+                self.sendObject(obj)
47
+        else:
48
+            self.request.sendall(b"%% NOT FOUND\n\n")
49
+
50
+    def sendObject(self, obj):
51
+        result = [
52
+            "",
53
+            "%% Object %s (%s)" % (obj, obj.getClassName()),
54
+            ""
55
+        ]
56
+
57
+        for field, value in getWhoisObjectFields(obj, False):
58
+            fieldName = field.lower().replace(" ", "-") + ":"
59
+            if not value:
60
+                value = ""
61
+
62
+            if hasattr(value, "through"):
63
+                for v in value.all():
64
+                    result.append("%-16s %s" % (fieldName, v))
65
+            else:
66
+                result.append("%-16s %s" % (fieldName, value))
67
+        result.extend(["", ""])
68
+
69
+        self.request.sendall("\n".join(result).encode())
70 70
 
71 71
 class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
72
-	allow_reuse_address = True
72
+    allow_reuse_address = True
73 73
 
74 74
 
75 75
 def _parser():
76
-	parser = argparse.ArgumentParser(
77
-	             #prog='foo',
78
-	             #description='do some awesome foo',
79
-	)
76
+    parser = argparse.ArgumentParser(
77
+                 #prog='foo',
78
+                 #description='do some awesome foo',
79
+    )
80 80
 
81
-	parser.add_argument("-p", "--port", default=43, type=int, help="whoisd port")
82
-	#parser.add_argument("-v", "--verbose", default=False, action="store_true", help="Be more verbose")
81
+    parser.add_argument("-p", "--port", default=43, type=int, help="whoisd port")
82
+    #parser.add_argument("-v", "--verbose", default=False, action="store_true", help="Be more verbose")
83 83
 
84
-	parser.add_argument("--version", action="version", version="%(prog)s " + __VERSION__)
84
+    parser.add_argument("--version", action="version", version="%(prog)s " + __VERSION__)
85 85
 
86
-	return parser
86
+    return parser
87 87
 
88 88
 def main():
89
-	parser = _parser()
90
-	args = parser.parse_args()
89
+    parser = _parser()
90
+    args = parser.parse_args()
91 91
 
92
-	server = ThreadedTCPServer(('', args.port), WhoisHandler)
92
+    server = ThreadedTCPServer(('', args.port), WhoisHandler)
93 93
 
94
-	while True:
95
-		try:
96
-			server.serve_forever()
97
-		except select.error as e:
98
-			log.exception(e)
99
-		except KeyboardInterrupt:
100
-			log.info("^c hit, quitting.")
101
-			break
94
+    while True:
95
+        try:
96
+            server.serve_forever()
97
+        except select.error as e:
98
+            log.exception(e)
99
+        except KeyboardInterrupt:
100
+            log.info("^c hit, quitting.")
101
+            break
102 102
 
103
-	server.server_close()
103
+    server.server_close()
104 104
 
105 105
 
106 106
 if __name__ == '__main__':
107
-	main()
107
+    main()

+ 3
- 3
dncore/forms.py View File

@@ -7,6 +7,6 @@ from .models import User
7 7
 
8 8
 
9 9
 class CustomUserCreationForm(UserCreationForm):
10
-	class Meta:
11
-		model = User
12
-		fields = ("username",)
10
+    class Meta:
11
+        model = User
12
+        fields = ("username",)

+ 1
- 1
dncore/models.py View File

@@ -7,4 +7,4 @@ from django.db import models
7 7
 from django.contrib.auth.models import AbstractUser
8 8
 
9 9
 class User(AbstractUser):
10
-	pass
10
+    pass

+ 5
- 5
dncore/urls.py View File

@@ -8,10 +8,10 @@ from django.contrib.auth import views as auth_views
8 8
 from . import views as dncore_views
9 9
 
10 10
 urlpatterns = [
11
-	url(r'^$', dncore_views.dashboard, name='dashboard'),
11
+    url(r'^$', dncore_views.dashboard, name='dashboard'),
12 12
 
13
-	url(r'^login/$', auth_views.login, name='login'),
14
-	url(r'^register/$', dncore_views.RegisterUser.as_view(), name='register'),
15
-	url(r'^logout/$', auth_views.logout, {'next_page': '/'}, name='logout'),
16
-	url(r'^profile/$', dncore_views.profile, name='profile'),
13
+    url(r'^login/$', auth_views.login, name='login'),
14
+    url(r'^register/$', dncore_views.RegisterUser.as_view(), name='register'),
15
+    url(r'^logout/$', auth_views.logout, {'next_page': '/'}, name='logout'),
16
+    url(r'^profile/$', dncore_views.profile, name='profile'),
17 17
 ]

+ 32
- 32
dncore/views.py View File

@@ -22,54 +22,54 @@ from .forms import CustomUserCreationForm
22 22
 
23 23
 @login_required
24 24
 def profile(request):
25
-	pwForm = None
25
+    pwForm = None
26 26
 
27
-	if request.method == "POST":
28
-		if request.POST.get("submit", None) == "pwchange":
29
-			pwForm = PasswordChangeForm(user=request.user, data=request.POST)
30
-			if pwForm.is_valid():
31
-				pwForm.save()
32
-				auth_login(request, pwForm.user)
33
-				messages.success(request, "Password changed")
27
+    if request.method == "POST":
28
+        if request.POST.get("submit", None) == "pwchange":
29
+            pwForm = PasswordChangeForm(user=request.user, data=request.POST)
30
+            if pwForm.is_valid():
31
+                pwForm.save()
32
+                auth_login(request, pwForm.user)
33
+                messages.success(request, "Password changed")
34 34
 
35
-				return HttpResponseRedirect(reverse("user:profile"))
35
+                return HttpResponseRedirect(reverse("user:profile"))
36 36
 
37
-	if not pwForm:
38
-		pwForm = PasswordChangeForm(user=request.user)
37
+    if not pwForm:
38
+        pwForm = PasswordChangeForm(user=request.user)
39 39
 
40
-	return render(request, "registration/profile.html", {'pwForm': pwForm})
40
+    return render(request, "registration/profile.html", {'pwForm': pwForm})
41 41
 
42 42
 
43 43
 @login_required
44 44
 def dashboard(request):
45
-	mnts = request.user.maintainer_set.all()
46
-	ownMnts = request.user.maintainer_set.filter(rir=False, lir=False).all().distinct()
47
-	# if account only has rir/lir objects, show them
48
-	if ownMnts.count() == 0:
49
-		ownMnts = mnts
45
+    mnts = request.user.maintainer_set.all()
46
+    ownMnts = request.user.maintainer_set.filter(rir=False, lir=False).all().distinct()
47
+    # if account only has rir/lir objects, show them
48
+    if ownMnts.count() == 0:
49
+        ownMnts = mnts
50 50
 
51
-	asns = ASNumber.objects.filter(Q(mnt_by__in=ownMnts) | Q(mnt_lower__in=ownMnts)).distinct()
52
-	inetnums = InetNum.objects.filter(Q(mnt_by__in=ownMnts) | Q(mnt_lower__in=ownMnts)).distinct()
53
-	domains = Domain.objects.filter(mnt_by__in=mnts).distinct()
54
-	rrequests = Request.objects.filter((Q(provider__in=mnts) | Q(applicant__in=mnts)) & Q(status=Request.STATE_OPEN)).distinct()
51
+    asns = ASNumber.objects.filter(Q(mnt_by__in=ownMnts) | Q(mnt_lower__in=ownMnts)).distinct()
52
+    inetnums = InetNum.objects.filter(Q(mnt_by__in=ownMnts) | Q(mnt_lower__in=ownMnts)).distinct()
53
+    domains = Domain.objects.filter(mnt_by__in=mnts).distinct()
54
+    rrequests = Request.objects.filter((Q(provider__in=mnts) | Q(applicant__in=mnts)) & Q(status=Request.STATE_OPEN)).distinct()
55 55
 
56
-	return render(request, "dncore/dashboard.html", {"asns": asns, "inetnums": inetnums, "domains": domains, 'rrequests': rrequests})
56
+    return render(request, "dncore/dashboard.html", {"asns": asns, "inetnums": inetnums, "domains": domains, 'rrequests': rrequests})
57 57
 
58 58
 
59 59
 def index(request):
60
-	if request.user.is_authenticated():
61
-		return HttpResponseRedirect(reverse("dashboard"))
60
+    if request.user.is_authenticated():
61
+        return HttpResponseRedirect(reverse("dashboard"))
62 62
 
63
-	return render(request, "index.html", {})
63
+    return render(request, "index.html", {})
64 64
 
65 65
 
66 66
 class RegisterUser(CreateView):
67
-	template_name = "dncore/registration.html"
68
-	form_class = CustomUserCreationForm
69
-	success_url = reverse_lazy("user:login")
67
+    template_name = "dncore/registration.html"
68
+    form_class = CustomUserCreationForm
69
+    success_url = reverse_lazy("user:login")
70 70
 
71
-	def form_valid(self, form):
72
-		ret = super(RegisterUser, self).form_valid(form)
73
-		messages.success(self.request, "You successfully registered as user %s and can now log in!" % form.instance.username)
71
+    def form_valid(self, form):
72
+        ret = super(RegisterUser, self).form_valid(form)
73
+        messages.success(self.request, "You successfully registered as user %s and can now log in!" % form.instance.username)
74 74
 
75
-		return ret
75
+        return ret

+ 52
- 52
dnmgmt/settings.py View File

@@ -21,7 +21,7 @@ from django.contrib import messages
21 21
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
22 22
 
23 23
 TLD_NAMESERVERS = [
24
-	'10.100.100.100',
24
+    '10.100.100.100',
25 25
 ]
26 26
 
27 27
 DATETIME_FORMAT = "Y-m-d H:i:s"
@@ -42,47 +42,47 @@ ALLOWED_HOSTS = ["*"]
42 42
 # Application definition
43 43
 
44 44
 INSTALLED_APPS = [
45
-	'django.contrib.admin',
46
-	'django.contrib.auth',
47
-	'django.contrib.contenttypes',
48
-	'django.contrib.sessions',
49
-	'django.contrib.messages',
50
-	'django.contrib.staticfiles',
51
-	'crispy_forms',
52
-	'formtools',
53
-	'dncore',
54
-	'whoisdb',
55
-	'rrequests',
56
-	'domains',
57
-	'api',
45
+    'django.contrib.admin',
46
+    'django.contrib.auth',
47
+    'django.contrib.contenttypes',
48
+    'django.contrib.sessions',
49
+    'django.contrib.messages',
50
+    'django.contrib.staticfiles',
51
+    'crispy_forms',
52
+    'formtools',
53
+    'dncore',
54
+    'whoisdb',
55
+    'rrequests',
56
+    'domains',
57
+    'api',
58 58
 ]
59 59
 
60 60
 MIDDLEWARE = [
61
-	'django.middleware.security.SecurityMiddleware',
62
-	'django.contrib.sessions.middleware.SessionMiddleware',
63
-	'django.middleware.common.CommonMiddleware',
64
-	'django.middleware.csrf.CsrfViewMiddleware',
65
-	'django.contrib.auth.middleware.AuthenticationMiddleware',
66
-	'django.contrib.messages.middleware.MessageMiddleware',
67
-	'django.middleware.clickjacking.XFrameOptionsMiddleware',
61
+    'django.middleware.security.SecurityMiddleware',
62
+    'django.contrib.sessions.middleware.SessionMiddleware',
63
+    'django.middleware.common.CommonMiddleware',
64
+    'django.middleware.csrf.CsrfViewMiddleware',
65
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
66
+    'django.contrib.messages.middleware.MessageMiddleware',
67
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
68 68
 ]
69 69
 
70 70
 ROOT_URLCONF = 'dnmgmt.urls'
71 71
 
72 72
 TEMPLATES = [
73
-	{
74
-		'BACKEND': 'django.template.backends.django.DjangoTemplates',
75
-		'DIRS': [os.path.join(BASE_DIR, "templates/")],
76
-		'APP_DIRS': True,
77
-		'OPTIONS': {
78
-			'context_processors': [
79
-				'django.template.context_processors.debug',
80
-				'django.template.context_processors.request',
81
-				'django.contrib.auth.context_processors.auth',
82
-				'django.contrib.messages.context_processors.messages',
83
-			],
84
-		},
85
-	},
73
+    {
74
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
75
+        'DIRS': [os.path.join(BASE_DIR, "templates/")],
76
+        'APP_DIRS': True,
77
+        'OPTIONS': {
78
+            'context_processors': [
79
+                'django.template.context_processors.debug',
80
+                'django.template.context_processors.request',
81
+                'django.contrib.auth.context_processors.auth',
82
+                'django.contrib.messages.context_processors.messages',
83
+            ],
84
+        },
85
+    },
86 86
 ]
87 87
 
88 88
 WSGI_APPLICATION = 'dnmgmt.wsgi.application'
@@ -92,10 +92,10 @@ WSGI_APPLICATION = 'dnmgmt.wsgi.application'
92 92
 # https://docs.djangoproject.com/en/1.10/ref/settings/#databases
93 93
 
94 94
 DATABASES = {
95
-	'default': {
96
-		'ENGINE': 'django.db.backends.sqlite3',
97
-		'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
98
-	}
95
+    'default': {
96
+        'ENGINE': 'django.db.backends.sqlite3',
97
+        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
98
+    }
99 99
 }
100 100
 
101 101
 
@@ -103,18 +103,18 @@ DATABASES = {
103 103
 # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
104 104
 
105 105
 AUTH_PASSWORD_VALIDATORS = [
106
-	{
107
-		'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
108
-	},
109
-	{
110
-		'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
111
-	},
112
-	{
113
-		'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
114
-	},
115
-	{
116
-		'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
117
-	},
106
+    {
107
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
108
+    },
109
+    {
110
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
111
+    },
112
+    {
113
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
114
+    },
115
+    {
116
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
117
+    },
118 118
 ]
119 119
 
120 120
 
@@ -139,11 +139,11 @@ USE_TZ = True
139 139
 STATIC_URL = '/static/'
140 140
 STATIC_ROOT = os.path.join(BASE_DIR, "cstatic")
141 141
 STATICFILES_DIRS = [
142
-	os.path.join(BASE_DIR, "static"),
142
+    os.path.join(BASE_DIR, "static"),
143 143
 ]
144 144
 CRISPY_TEMPLATE_PACK = 'bootstrap3'
145 145
 MESSAGE_TAGS = {
146
-	messages.ERROR: 'danger',
146
+    messages.ERROR: 'danger',
147 147
 }
148 148
 
149 149
 AUTH_USER_MODEL = 'dncore.User'

+ 8
- 8
dnmgmt/urls.py View File

@@ -30,15 +30,15 @@ import api.urls
30 30
 
31 31
 
32 32
 urlpatterns = [
33
-	url(r'^$', dncore.views.index, name='index'),
34
-	url(r'^dashboard/$', dncore.views.dashboard, name='dashboard'),
33
+    url(r'^$', dncore.views.index, name='index'),
34
+    url(r'^dashboard/$', dncore.views.dashboard, name='dashboard'),
35 35
 
36 36
     url(r'^admin/', admin.site.urls),
37
-	url(r'^user/', include(dncore.urls, namespace='user')),
38
-	url(r'^whoisdb/', include(whoisdb.urls, namespace='whoisdb')),
39
-	url(r'^rrequests/', include(rrequests.urls, namespace='rrequests')),
40
-	url(r'^domains/', include(domains.urls, namespace='domains')),
41
-	url(r'^api/', include(api.urls, namespace='api')),
37
+    url(r'^user/', include(dncore.urls, namespace='user')),
38
+    url(r'^whoisdb/', include(whoisdb.urls, namespace='whoisdb')),
39
+    url(r'^rrequests/', include(rrequests.urls, namespace='rrequests')),
40
+    url(r'^domains/', include(domains.urls, namespace='domains')),
41
+    url(r'^api/', include(api.urls, namespace='api')),
42 42
 
43
-	url(r'^help/$', TemplateView.as_view(template_name='help/help.html'), name='help'),
43
+    url(r'^help/$', TemplateView.as_view(template_name='help/help.html'), name='help'),
44 44
 ]

+ 206
- 175
domains/forms.py View File

@@ -15,229 +15,260 @@ import re
15 15
 import ipaddress
16 16
 
17 17
 
18
-class DomainForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
19
-	class Meta:
20
-		model = Domain
21
-		fields = ['name', 'nameservers', 'mnt_by', 'admin_c']
18
+class DSRecordMixin(object):
19
+    ds_re = re.compile(r"^(?P<id>\d+)\s+(?P<crypto>\d+)\s+(?P<hashtype>\d+)\s+(?P<hash>[0-9a-fA-F]+)$")
20
+    HASH_SUPPORTED = (1, 2)
21
+    CRYPTO_SUPPORTED = (3, 5, 6, 8, 10, 13, 15)
22 22
 
23
-	def __init__(self, *args, **kwargs):
24
-		super(DomainForm, self).__init__(*args, **kwargs)
23
+    def clean_ds_records(self):
24
+        ds_records = self.cleaned_data['ds_records'].strip()
25
+        result = []
25 26
 
26
-		mnts = self._user.maintainer_set.all()
27
-		self.fields['nameservers'].queryset = Nameserver.objects.filter(Q(mnt_by__in=mnts))
27
+        if not ds_records:
28
+            return ''
28 29
 
29
-		instance = getattr(self, "instance", None)
30
-		self._create = not (instance and instance.pk)
30
+        for n, rec in enumerate(ds_records.split("\n"), 1):
31
+            rec = rec.strip()
32
+            m = self.ds_re.match(rec)
33
+            if not m:
34
+                raise forms.ValidationError("Could not parse records {} - needs to be in format "
35
+                                            "'<id> <crypto> <hashtype> <hash>'".format(n))
31 36
 
32
-		if not self._create:
33
-			self.fields['name'].disabled = True
34
-			self.fields['nameservers'].queryset |= self.instance.nameservers.all()
37
+            if int(m.group('hashtype')) not in self.HASH_SUPPORTED:
38
+                raise forms.ValidationError("Record {} has an invalid hashtype of {}, supported are {}"
39
+                                            "".format(n, m.group('hashtype'), " ".join(map(str, self.HASH_SUPPORTED))))
40
+            if int(m.group('crypto')) not in self.CRYPTO_SUPPORTED:
41
+                raise forms.ValidationError("Record {} has unsupported crypto {}, supported are {}"
42
+                                            "".format(n, m.group('crypto'), " ".join(map(str, self.CRYPTO_SUPPORTED))))
35 43
 
36
-		self.fields['nameservers'].queryset  = self.fields['nameservers'].queryset.distinct()
44
+            result.append("{id} {crypto} {hashtype} {hash}".format(**m.groupdict()))
37 45
 
38
-	def clean_name(self):
39
-		name = self.cleaned_data['name'].lower()
40
-		if self._create:
41
-			if not name.endswith("."):
42
-				name += "."
46
+        return "\n".join(result)
43 47
 
44
-			if not name.endswith("dn."):
45
-				raise forms.ValidationError("Only .dn domains can be registered at this point")
46 48
 
47
-			if name.count(".") > 2:
48
-				raise forms.ValidationError("No subdomains can be registered")
49
+class DomainForm(MntFormMixin, WhoisObjectFormMixin, DSRecordMixin, forms.ModelForm):
50
+    class Meta:
51
+        model = Domain
52
+        fields = ['name', 'nameservers', 'mnt_by', 'admin_c', 'ds_records']
49 53
 
50
-			if not re.match("^[a-z0-9.-]+$", name):
51
-				raise forms.ValidationError("Only a-z, 0-9 and - are allowed inside the domain name")
54
+    def __init__(self, *args, **kwargs):
55
+        super(DomainForm, self).__init__(*args, **kwargs)
52 56
 
53
-			try:
54
-				Domain.objects.get(name=name)
55
-				raise forms.ValidationError("Domain already exists")
56
-			except Domain.DoesNotExist:
57
-				pass
57
+        mnts = self._user.maintainer_set.all()
58
+        self.fields['nameservers'].queryset = Nameserver.objects.filter(Q(mnt_by__in=mnts))
58 59
 
59
-		return name
60
+        instance = getattr(self, "instance", None)
61
+        self._create = not (instance and instance.pk)
62
+
63
+        if not self._create:
64
+            self.fields['name'].disabled = True
65
+            self.fields['nameservers'].queryset |= self.instance.nameservers.all()
66
+
67
+        self.fields['nameservers'].queryset  = self.fields['nameservers'].queryset.distinct()
68
+
69
+    def clean_name(self):
70
+        name = self.cleaned_data['name'].lower()
71
+        if self._create:
72
+            if not name.endswith("."):
73
+                name += "."
74
+
75
+            if not name.endswith("dn."):
76
+                raise forms.ValidationError("Only .dn domains can be registered at this point")
77
+
78
+            if name.count(".") > 2:
79
+                raise forms.ValidationError("No subdomains can be registered")
80
+
81
+            if not re.match("^[a-z0-9.-]+$", name):
82
+                raise forms.ValidationError("Only a-z, 0-9 and - are allowed inside the domain name")
83
+
84
+            try:
85
+                Domain.objects.get(name=name)
86
+                raise forms.ValidationError("Domain already exists")
87
+            except Domain.DoesNotExist:
88
+                pass
89
+
90
+        return name
60 91
 
61 92
 
62 93
 class NameserverForm(MntFormMixin, WhoisObjectFormMixin, forms.ModelForm):
63
-	class Meta:
64
-		model = Nameserver
65
-		fields = ['name', 'glueIPv4', 'glueIPv6', 'mnt_by', 'admin_c']
66
-
67
-		help_texts = {
68
-			"glueIPv4": "Note: You can only set a glue record if the base domain of this nameserver belongs to you!"
69
-		}
70
-
71
-	def __init__(self, *args, **kwargs):
72
-		super(NameserverForm, self).__init__(*args, **kwargs)
73
-
74
-		instance = getattr(self, "instance", None)
75
-		self._create = not (instance and instance.pk)
76
-
77
-	def cleanNetwork(self, glue):
78
-		ip = ipaddress.ip_address(glue)
79
-		proto = InetNum.IPv4 if ip.version == 4 else InetNum.IPv6
80
-		nets = InetNum.objects.filter(parent_range=None, protocol=proto)
81
-
82
-		if len(nets) == 0:
83
-			raise forms.ValidationError("No range has been registered for IPv%s in the whois interface" % ip.version)
84
-
85
-		for net in nets:
86
-			if ip in net.getNetwork():
87
-				break
88
-		else:
89
-			raise forms.ValidationError("Glue record address is not inside DarkNet (subnet %s)" % ", ".join(map(lambda _x: _x.prefix(), nets)))
90
-
91
-	def clean_glueIPv4(self):
92
-		glue = self.cleaned_data['glueIPv4']
93
-
94
-		if glue:
95
-			self.cleanNetwork(glue)
96
-
97
-		return glue
98
-
99
-	def clean_glueIPv6(self):
100
-		glue = self.cleaned_data['glueIPv6']
94
+    class Meta:
95
+        model = Nameserver
96
+        fields = ['name', 'glueIPv4', 'glueIPv6', 'mnt_by', 'admin_c']
97
+
98
+        help_texts = {
99
+            "glueIPv4": "Note: You can only set a glue record if the base domain of this nameserver belongs to you!"
100
+        }
101
+
102
+    def __init__(self, *args, **kwargs):
103
+        super(NameserverForm, self).__init__(*args, **kwargs)
104
+
105
+        instance = getattr(self, "instance", None)
106
+        self._create = not (instance and instance.pk)
107
+
108
+    def cleanNetwork(self, glue):
109
+        ip = ipaddress.ip_address(glue)
110
+        proto = InetNum.IPv4 if ip.version == 4 else InetNum.IPv6
111
+        nets = InetNum.objects.filter(parent_range=None, protocol=proto)
112
+
113
+        if len(nets) == 0:
114
+            raise forms.ValidationError("No range has been registered for IPv%s in the whois interface" % ip.version)
115
+
116
+        for net in nets:
117
+            if ip in net.getNetwork():
118
+                break
119
+        else:
120
+            raise forms.ValidationError("Glue record address is not inside DarkNet (subnet %s)" % ", ".join(map(lambda _x: _x.prefix(), nets)))
121
+
122
+    def clean_glueIPv4(self):
123
+        glue = self.cleaned_data['glueIPv4']
124
+
125
+        if glue:
126
+            self.cleanNetwork(glue)
127
+
128
+        return glue
129
+
130
+    def clean_glueIPv6(self):
131
+        glue = self.cleaned_data['glueIPv6']
101 132
 
102
-		if glue:
103
-			self.cleanNetwork(glue)
133
+        if glue:
134
+            self.cleanNetwork(glue)
104 135
 
105
-		return glue
136
+        return glue
106 137
 
107
-	def clean_name(self):
108
-		name = self.cleaned_data['name'].lower().strip()
109
-		if not name.endswith("."):
110
-			name += "."
138
+    def clean_name(self):
139
+        name = self.cleaned_data['name'].lower().strip()
140
+        if not name.endswith("."):
141
+            name += "."
111 142
 
112
-		# allow name to stay if it did not change
113
-		if not self._create and self.instance.name == name:
114
-			return name
143
+        # allow name to stay if it did not change
144
+        if not self._create and self.instance.name == name:
145
+            return name
115 146
 
116
-		if name.count(".") <= 2:
117
-			raise forms.ValidationError("Nameserver must be inside a domain (e.g. ns1.noot.dn.)")
147
+        if name.count(".") <= 2:
148
+            raise forms.ValidationError("Nameserver must be inside a domain (e.g. ns1.noot.dn.)")
118 149
 
119
-		mnts = self._user.maintainer_set.all()
120
-		try:
121
-			obj = Nameserver.objects.get(name=name, mnt_by__in=mnts)
122
-			if self._create or not self._create and obj.pk != self.instance.pk:
123
-				raise forms.ValidationError("You already have a nameserver with this name under your control")
124
-		except Nameserver.DoesNotExist:
125
-			pass
126
-		except Nameserver.MultipleObjectsReturned:
127
-			pass
150
+        mnts = self._user.maintainer_set.all()
151
+        try:
152
+            obj = Nameserver.objects.get(name=name, mnt_by__in=mnts)
153
+            if self._create or not self._create and obj.pk != self.instance.pk:
154
+                raise forms.ValidationError("You already have a nameserver with this name under your control")
155
+        except Nameserver.DoesNotExist:
156
+            pass
157
+        except Nameserver.MultipleObjectsReturned:
158
+            pass
128 159
 
129
-		return name
160
+        return name
130 161
 
131
-	def clean(self):
132
-		cleaned_data = super(NameserverForm, self).clean()
162
+    def clean(self):
163
+        cleaned_data = super(NameserverForm, self).clean()
133 164
 
134
-		if not self.errors:
135
-			name = cleaned_data.get("name")
136
-			mntBy = cleaned_data.get("mnt_by")
137
-			zone = ".".join(name.split(".")[-3:])
138
-			ipv4 = cleaned_data.get("glueIPv4", None)
139
-			ipv6 = cleaned_data.get("glueIPv6", None)
165
+        if not self.errors:
166
+            name = cleaned_data.get("name")
167
+            mntBy = cleaned_data.get("mnt_by")
168
+            zone = ".".join(name.split(".")[-3:])
169
+            ipv4 = cleaned_data.get("glueIPv4", None)
170
+            ipv6 = cleaned_data.get("glueIPv6", None)
140 171
 
141
-			if not ipv4:
142
-				ipv4 = None
143
-			if not ipv6:
144
-				ipv6 = None
172
+            if not ipv4:
173
+                ipv4 = None
174
+            if not ipv6:
175
+                ipv6 = None
145 176
 
146
-			if self._create and (ipv4 or ipv6)  or not self._create and not (self.instance.glueIPv4 == ipv4 and self.instance.glueIPv6 == ipv6):
147
-				mnts = self._user.maintainer_set.all()
148
-				domains = Domain.objects.filter(mnt_by__in=mnts)
149
-				found = False
150
-				for domain in domains:
151
-					if domain.name == zone:
152
-						found = True
153
-						break
177
+            if self._create and (ipv4 or ipv6)  or not self._create and not (self.instance.glueIPv4 == ipv4 and self.instance.glueIPv6 == ipv6):
178
+                mnts = self._user.maintainer_set.all()
179
+                domains = Domain.objects.filter(mnt_by__in=mnts)
180
+                found = False
181
+                for domain in domains:
182
+                    if domain.name == zone:
183
+                        found = True
184
+                        break
154 185
 
155
-				if not found:
156
-					raise forms.ValidationError("You have glue IPs set, but this domain is not under a domain you control.")
186
+                if not found:
187
+                    raise forms.ValidationError("You have glue IPs set, but this domain is not under a domain you control.")
157 188
 
158
-			if ipv4 or ipv6:
159
-				try:
160
-					ns = Nameserver.objects.get(Q(name=name) & (Q(glueIPv4__isnull=False) | Q(glueIPv6__isnull=False)))
161
-					if self._create or ns.pk != self.instance.pk:
162
-						nsMnts = ", ".join(n.handle for n in ns.mnt_by.all())
189
+            if ipv4 or ipv6:
190
+                try:
191
+                    ns = Nameserver.objects.get(Q(name=name) & (Q(glueIPv4__isnull=False) | Q(glueIPv6__isnull=False)))
192
+                    if self._create or ns.pk != self.instance.pk:
193
+                        nsMnts = ", ".join(n.handle for n in ns.mnt_by.all())
163 194
 
164
-						raise forms.ValidationError("Only one nameserver for this domain can have glue records and one already exists (maintained by %s)" % nsMnts)
165
-				except Nameserver.DoesNotExist:
166
-					pass
195
+                        raise forms.ValidationError("Only one nameserver for this domain can have glue records and one already exists (maintained by %s)" % nsMnts)
196
+                except Nameserver.DoesNotExist:
197
+                    pass
167 198
 
168
-			failedMnts = set()
169
-			for ns in Nameserver.objects.filter(name=name, mnt_by__in=mntBy):
170
-				if self._create or self.instance.pk != ns.pk:
171
-					for mnt in ns.mnt_by.all():
172
-						if mnt in mntBy:
173
-							failedMnts.add(mnt.handle)
199
+            failedMnts = set()
200
+            for ns in Nameserver.objects.filter(name=name, mnt_by__in=mntBy):
201
+                if self._create or self.instance.pk != ns.pk:
202
+                    for mnt in ns.mnt_by.all():
203
+                        if mnt in mntBy:
204
+                            failedMnts.add(mnt.handle)
174 205
 
175
-			if len(failedMnts) > 0:
176
-				raise forms.ValidationError("The following maintainer objects already have this nameservers: %s" % ", ".join(failedMnts))
177
-				
178
-				
179
-		return cleaned_data
206
+            if len(failedMnts) > 0:
207
+                raise forms.ValidationError("The following maintainer objects already have this nameservers: %s" % ", ".join(failedMnts))
208
+                
209
+                
210
+        return cleaned_data
180 211
 
181 212
 
182
-class ReverseZoneForm(forms.ModelForm):
183
-	prefix = forms.CharField(validators=[IP46CIDRValidator])
213
+class ReverseZoneForm(DSRecordMixin, forms.ModelForm):
214
+    prefix = forms.CharField(validators=[IP46CIDRValidator])
184 215
 
185
-	class Meta:
186
-		model = ReverseZone
187
-		fields = ['parentNet', 'nameservers']
216
+    class Meta:
217
+        model = ReverseZone
218
+        fields = ['parentNet', 'nameservers']
188 219
 
189
-		help_texts = {
190
-			"prefix": "The prefix in CIDR form for which this object is responsible",
191
-		}
220
+        help_texts = {
221
+            "prefix": "The prefix in CIDR form for which this object is responsible",
222
+        }
192 223
 
193
-	def __init__(self, user, *args, **kwargs):
194
-		self._user = user
224
+    def __init__(self, user, *args, **kwargs):
225
+        self._user = user
195 226
 
196
-		super(ReverseZoneForm, self).__init__(*args, **kwargs)
227
+        super(ReverseZoneForm, self).__init__(*args, **kwargs)
197 228
 
198
-		instance = getattr(self, "instance", None)
199
-		self._create = not (instance and instance.pk)
229
+        instance = getattr(self, "instance", None)
230
+        self._create = not (instance and instance.pk)
200 231
 
201
-		mnts = self._user.maintainer_set.all()
202
-		self.fields['parentNet'].queryset = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct()
232
+        mnts = self._user.maintainer_set.all()
233
+        self.fields['parentNet'].queryset = InetNum.objects.filter(Q(mnt_by__in=mnts) | Q(mnt_lower__in=mnts)).distinct()
203 234
 
204
-		self.fields['nameservers'].queryset = Nameserver.objects.filter(Q(mnt_by__in=mnts))
235
+        self.fields['nameservers'].queryset = Nameserver.objects.filter(Q(mnt_by__in=mnts))
205 236
 
206
-		if not self._create:
207
-			self.fields['prefix'].disabled = True
208
-			self.fields['nameservers'].queryset |= self.instance.nameservers.all()
237
+        if not self._create:
238
+            self.fields['prefix'].disabled = True
239
+            self.fields['nameservers'].queryset |= self.instance.nameservers.all()
209 240
 
210
-		self.fields['nameservers'].queryset = self.fields['nameservers'].queryset.distinct()
241
+        self.fields['nameservers'].queryset = self.fields['nameservers'].queryset.distinct()
211 242
 
212
-	def clean_prefix(self):
213
-		prefix = self.cleaned_data['prefix']
243
+    def clean_prefix(self):
244
+        prefix = self.cleaned_data['prefix']
214 245
 
215
-		net = ipaddress.ip_network(prefix)
216
-		if net.version == 6 and net.prefixlen % 4 != 0:
217
-			raise forms.ValidationError("IPv6 reverse zone prefix length has to be a multiple of 4")
246
+        net = ipaddress.ip_network(prefix)
247
+        if net.version == 6 and net.prefixlen % 4 != 0:
248
+            raise forms.ValidationError("IPv6 reverse zone prefix length has to be a multiple of 4")
218 249
 
219
-		return prefix
250
+        return prefix
220 251
 
221
-	def clean(self):
222
-		cleaned_data = super(ReverseZoneForm, self).clean()
252
+    def clean(self):
253
+        cleaned_data = super(ReverseZoneForm, self).clean()
223 254
 
224
-		if not self.errors:
225
-			if self._create:
226
-				net = ipaddress.ip_network(cleaned_data['prefix'])
227
-				parentNet = cleaned_data['parentNet'].getNetwork()
255
+        if not self.errors:
256
+            if self._create:
257
+                net = ipaddress.ip_network(cleaned_data['prefix'])
258
+                parentNet = cleaned_data['parentNet'].getNetwork()
228 259
 
229
-				if net.network_address not in parentNet:
230
-					raise forms.ValidationError("Given prefix %s is not inside of parent netblock %s" % (net, parentNet))
260
+                if net.network_address not in parentNet:
261
+                    raise forms.ValidationError("Given prefix %s is not inside of parent netblock %s" % (net, parentNet))
231 262
 
232
-				# For now just check all the zones...
233
-				#zones = ReverseZone.objects.filter(parentNet=cleaned_data['parentNet'])
234
-				zones = ReverseZone.objects.all()
235
-				for zone in zones:
236
-					if net.network_address in zone.parentNet.getNetwork():
237
-						raise forms.ValidationError("Given prefix already has a reverse zone object associated to it: %s" % zone)
263
+                # For now just check all the zones...
264
+                #zones = ReverseZone.objects.filter(parentNet=cleaned_data['parentNet'])
265
+                zones = ReverseZone.objects.all()
266
+                for zone in zones:
267
+                    if net.network_address in zone.parentNet.getNetwork():
268
+                        raise forms.ValidationError("Given prefix already has a reverse zone object associated to it: %s" % zone)
238 269
 
239
-				self.instance.address = str(net.network_address)
240
-				self.instance.netmask = net.prefixlen
270
+                self.instance.address = str(net.network_address)
271
+                self.instance.netmask = net.prefixlen
241 272
 
242 273
 
243
-		return cleaned_data
274
+        return cleaned_data

+ 25
- 0
domains/migrations/0004_auto_20170403_0533.py View File

@@ -0,0 +1,25 @@
1
+# -*- coding: utf-8 -*-
2
+# Generated by Django 1.10.5 on 2017-04-03 05:33
3
+from __future__ import unicode_literals
4
+
5
+from django.db import migrations, models
6
+
7
+
8
+class Migration(migrations.Migration):
9
+
10
+    dependencies = [
11
+        ('domains', '0003_auto_20170322_1801'),
12
+    ]
13
+
14
+    operations = [
15
+        migrations.AlterField(
16
+            model_name='domain',
17
+            name='mnt_by',
18
+            field=models.ManyToManyField(help_text='You can select multiple maintainers here', to='whoisdb.Maintainer'),
19
+        ),
20
+        migrations.AlterField(
21
+            model_name='nameserver',
22
+            name='mnt_by',
23
+            field=models.ManyToManyField(help_text='You can select multiple maintainers here', to='whoisdb.Maintainer'),
24
+        ),
25
+    ]

+ 20
- 0
domains/migrations/0005_domain_ds_records.py View File

@@ -0,0 +1,20 @@
1
+# -*- coding: utf-8 -*-
2
+# Generated by Django 1.11 on 2019-05-30 21:18
3
+from __future__ import unicode_literals
4
+
5
+from django.db import migrations, models
6
+
7
+
8
+class Migration(migrations.Migration):
9
+
10
+    dependencies = [
11
+        ('domains', '0004_auto_20170403_0533'),
12
+    ]
13
+
14
+    operations = [
15
+        migrations.AddField(
16
+            model_name='domain',
17
+            name='ds_records',
18
+            field=models.TextField(blank=True),
19
+        ),
20
+    ]

+ 114
- 107
domains/models.py View File

@@ -13,139 +13,146 @@ import ipaddress
13 13
 # allow owners of a subnet to create reverse dns record?
14 14
 
15 15
 
16
+class DSRecordModelMixin(models.Model):
17
+    class Meta:
18
+        abstract = True
19
+
20
+    ds_records = models.TextField(blank=True, verbose_name='DS Records',
21
+                                  help_text='DS Records in the format of [id] [crypto-algo] [hash-algo] [hash]')
22
+
16 23
 class Nameserver(MntdObject):
17
-	handleSuffix = "NS"
18
-	# dns name
19
-	# ip address, if glue
20
-	#	ipv4/ipv6 address?
21
-	handle = None
22
-	name = models.CharField(max_length=256)
23
-	glueIPv4 = models.GenericIPAddressField(protocol='IPv4', blank=True, null=True)
24
-	glueIPv6 = models.GenericIPAddressField(protocol='IPv6', blank=True, null=True)
24
+    handleSuffix = "NS"
25
+    # dns name
26
+    # ip address, if glue
27
+    #    ipv4/ipv6 address?
28
+    handle = None
29
+    name = models.CharField(max_length=256)
30
+    glueIPv4 = models.GenericIPAddressField(protocol='IPv4', blank=True, null=True)
31
+    glueIPv6 = models.GenericIPAddressField(protocol='IPv6', blank=True, null=True)
25 32
 
26
-	admin_c = models.ManyToManyField(Contact)
33
+    admin_c = models.ManyToManyField(Contact)
27 34
 
28
-	def getPK(self):
29
-		return self.pk
30
-
31
-	def get_absolute_url(self):
32
-		return reverse("domains:nameserver-show", args=(self.pk,))
33
-
34
-	def __str__(self):
35
-		return self.name
36
-
37
-	def getAppName(self):
38
-		return "domains"
35
+    def getPK(self):
36
+        return self.pk
37
+
38
+    def get_absolute_url(self):
39
+        return reverse("domains:nameserver-show", args=(self.pk,))
40
+
41
+    def __str__(self):
42
+        return self.name
43
+
44
+    def getAppName(self):
45
+        return "domains"
39 46
 
40
-	def getNoDeleteReasons(self):
41
-		# nameservers can always be deleted
42
-		return []
47
+    def getNoDeleteReasons(self):
48
+        # nameservers can always be deleted
49
+        return []
43 50
 
44 51
 
45
-class Domain(MntdObject):
46
-	handle = None
47
-	handleSuffix = "DOM"
48
-	name = models.CharField(max_length=67, unique=True, db_index=True)
52
+class Domain(DSRecordModelMixin, MntdObject):
53
+    handle = None
54
+    handleSuffix = "DOM"
55
+    name = models.CharField(max_length=67, unique=True, db_index=True)
49 56
 
50
-	nameservers = models.ManyToManyField(Nameserver, blank=True)
51
-	admin_c = models.ManyToManyField(Contact)
57
+    nameservers = models.ManyToManyField(Nameserver, blank=True)
58
+    admin_c = models.ManyToManyField(Contact)
52 59
 
53
-	def getPK(self):
54
-		return self.name
60
+    def getPK(self):
61
+        return self.name
55 62
 
56
-	def getZone(self):
57
-		return self.name
63
+    def getZone(self):
64
+        return self.name
58 65
 
59
-	def get_absolute_url(self):
60
-		return reverse("domains:domain-show", args=(self.name,))
66
+    def get_absolute_url(self):
67
+        return reverse("domains:domain-show", args=(self.name,))
61 68
 
62
-	def __str__(self):
63
-		return self.name
69
+    def __str__(self):
70
+        return self.name
64 71
 
65
-	def getAppName(self):
66
-		return "domains"
72
+    def getAppName(self):
73
+        return "domains"
67 74
 
68
-	def getNoDeleteReasons(self):
69
-		reasons = []
75
+    def getNoDeleteReasons(self):
76
+        reasons = []
70 77
 
71
-		nameservers = Nameserver.objects.filter(name__endswith="." + self.name)
72
-		for ns in nameservers:
73
-			# FIXME: check if the nameserver has a glue record.... and also if it is maintained by us
74
-			reasons.append("Nameserver %s depends on this domain" % ns.name)
78
+        nameservers = Nameserver.objects.filter(name__endswith="." + self.name)
79
+        for ns in nameservers:
80
+            # FIXME: check if the nameserver has a glue record.... and also if it is maintained by us
81
+            reasons.append("Nameserver %s depends on this domain" % ns.name)
75 82
 
76
-		return reasons
83
+        return reasons
77 84
 
78
-	@classmethod
79
-	def fixName(clazz, name):
80
-		if not name.endswith("."):
81
-			name += "."
82
-		return name.lower()
85
+    @classmethod
86
+    def fixName(clazz, name):
87
+        if not name.endswith("."):
88
+            name += "."
89
+        return name.lower()
83 90
 
84 91
 
85
-class ReverseZone(WhoisObject):
86
-	handle = None
92
+class ReverseZone(DSRecordModelMixin, WhoisObject):
93
+    handle = None
87 94
 
88
-	parentNet = models.ForeignKey(InetNum)
89
-	address = models.GenericIPAddressField(db_index=True)
90
-	netmask = models.PositiveIntegerField()
95
+    parentNet = models.ForeignKey(InetNum)
96
+    address = models.GenericIPAddressField(db_index=True)
97
+    netmask = models.PositiveIntegerField()
91 98
 
92
-	nameservers = models.ManyToManyField(Nameserver)
99
+    nameservers = models.ManyToManyField(Nameserver)
93 100
 
94
-	def getPK(self):
95
-		return self.pk
101
+    def getPK(self):
102
+        return self.pk
96 103
 
97
-	def prefix(self):
98
-		""" Helper function, mainly used in templates """
99
-		return "%s/%s" % (self.address, self.netmask)
104
+    def prefix(self):
105
+        """ Helper function, mainly used in templates """
106
+        return "%s/%s" % (self.address, self.netmask)
100 107
 
101
-	def getNetwork(self):
102
-		return ipaddress.ip_network(self.prefix())
108
+    def getNetwork(self):
109
+        return ipaddress.ip_network(self.prefix())
103 110
 
104
-	def getZone(self):
105
-		net = self.parentNet.getNetwork()
106
-		if net.version == 4:
107
-			# does not work with python3.4
108
-			## for these we delegate the full domain
109
-			#if 0 < net.prefixlen < 32 and net.prefixlen % 8 == 0:
110
-			#	zoneParts = net.reverse_pointer.split(".")
111
+    def getZone(self):
112
+        net = self.parentNet.getNetwork()
113
+        if net.version == 4:
114
+            # does not work with python3.4
115
+            ## for these we delegate the full domain
116
+            #if 0 < net.prefixlen < 32 and net.prefixlen % 8 == 0:
117
+            #    zoneParts = net.reverse_pointer.split(".")
111 118
 
112
-			#	return ".".join(zoneParts[1:])
113
-			#else:
114
-			#	# return RFC2317 compliant zone
115
-			#	return net.reverse_pointer
116
-			parts = list(reversed(str(net.network_address).split(".")))
117
-			domain = ".".join(parts[4 - net.prefixlen // 8:]) + ".in-addr.arpa."
118
-			if net.prefixlen % 8 == 0:
119
-				# clean cut!
120
-				return domain
121
-			else:
122
-				# RFC2317 compliant!
123
-				rfc2317Domain = "%s/%s.%s" % (parts[4 - net.prefixlen // 8 - 1], net.prefixlen, domain)
124
-				return rfc2317Domain
125
-		else:
126
-			## does not work with python3.4
127
-			## ipv6
128
-			## thefuck ipaddress lib... _the_ _fuck_
129
-			#zoneParts = net.reverse_pointer.split(".")[-2 - net.prefixlen // 4:]
130